Compare commits

...

138 Commits

Author SHA1 Message Date
Aliaksandr Valialkin
ab4d9f6213 app/vlselect/vmui: run make vmui-logs-update after 17b3f24a37 2025-01-20 19:55:31 +01:00
Aliaksandr Valialkin
81c313fd89 docs/VictoriaLogs/CHANGELOG.md: cut v1.7.0-victorialogs 2025-01-20 19:41:21 +01:00
Aliaksandr Valialkin
e9de665289 vendor/github.com/VictoriaMetrics/metricsql: update from v0.81.1 to v0.82.0
This introduces the ability to copy-n-paste queries with $__interval and $__rate_interval
placeholders into VictoriaMetrics - these placeholders are automatically replaced with 1i,
which equals to the `step` query arg value passed to /api/v1/query_range and /api/v1/query.
2025-01-20 16:56:20 +01:00
Aliaksandr Valialkin
bfbe06e912 lib/logstorage: add ability to execute INNER JOIN with join pipe 2025-01-20 16:56:20 +01:00
Aliaksandr Valialkin
71a7d0db4a docs/VictoriaLogs/LogsQL.md: clarify docs about LogsQL pipes a bit 2025-01-20 16:55:22 +01:00
Aliaksandr Valialkin
e8748e4747 docs/VictoriaLogs/FAQ.md: add questions on how to determine the number of unique log streams and unique field values 2025-01-20 16:55:22 +01:00
hagen1778
ad3a5be097 deployment: bump vm-datasource version to v0.11.0
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-20 13:41:29 +01:00
Yury Molodov
17b3f24a37 vmui/logs: improvements to grouped view in logs (#7815)
### Describe Your Changes

This PR introduces improvements for the `Group view` tab:

1. **Reduced text size and improved styles**  
   Related issue: #7479
   <details>  
   <summary>Demo UI</summary>  
   
<img
src="https://github.com/user-attachments/assets/c96aed95-e668-49a5-af20-778580e8a884"/>
   
   </details>  

2. **Added the ability to select a field to display instead of `_msg`**
   You can select fields to display in the field list or in the settings
   Related issue: #7419
   <details>  
   <summary>Demo UI</summary>  
      
<img width="600"
src="https://github.com/user-attachments/assets/b5ae1433-bf40-4151-986b-ba057791dcee"/>
<img width="600"
src="https://github.com/user-attachments/assets/2c3d8c5c-1543-43ee-8241-8fd4d4b99b1d"/>

   </details>

3. **Added date format customization**  
   <details>  
   <summary>Demo UI</summary>  
      
<img width="400"
src="https://github.com/user-attachments/assets/b095adfd-cbe3-45a6-bd7d-dc574d3edfd1"/>
<img width="400"
src="https://github.com/user-attachments/assets/0e8b0fbc-2c01-40b7-b9b8-7ef04d21ef9b"/>
      
   </details>  

4. **Added an option to display log messages in a single line**  
   <details>  
   <summary>Demo UI</summary>  
   
<img width="400"
src="https://github.com/user-attachments/assets/43d64a57-19c9-4b15-9009-13b8545d906c"/>
<img
src="https://github.com/user-attachments/assets/eaada2b1-9474-4496-ac39-b38332e637c1"/>

   </details>  

5. **Added an option to display group labels in a single line**  
   <details>  
   <summary>Demo UI</summary>  

<img width="400"
src="https://github.com/user-attachments/assets/05be097a-7b19-4e7b-9cf4-181fd802728b"/>
<img
src="https://github.com/user-attachments/assets/d4405fa6-3829-4713-8537-11edc6da5c4f"/>
<img
src="https://github.com/user-attachments/assets/90d2c48d-ba3b-4051-a645-22e7c8945dcb"/>

   </details> 
   
7. **Settings indicator for modified defaults**  
A red marker is displayed on the settings icon if the default settings
are changed. Hovering over it shows all the modified parameters.
   <details>  
   <summary>Demo UI</summary>  

<img
src="https://github.com/user-attachments/assets/19fbaf9b-684b-4cac-ad80-2db5e63804e9"/>
   
   </details>
2025-01-20 12:59:06 +01:00
Zhu Jiekun
1f0b03aebe docs: update docs for *authKey, add authKey to HTTP 401 resp body (#7971)
### Describe Your Changes

optimize for
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6226

for user who set `*AuthKey` flag, they will receive new response in
body:
```go
// query arg not set
The provided authKey '' doesn't match -search.resetCacheAuthKey

// incorrect query arg
The provided authKey '5dxd71hsz==' doesn't match -search.resetCacheAuthKey
```

previously, they receive:
```
The provided authKey doesn't match -search.resetCacheAuthKey
```

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-20 12:42:53 +01:00
Github Actions
fc8710c071 Automatic update operator docs from VictoriaMetrics/operator@7da9289 (#8093)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2025-01-20 12:37:42 +01:00
Yury Molodov
a7f36eef0e vmui/logs: update default graph to bars with color fill #7101 (#8060)
### Describe Your Changes

Updated the default graph type in the hits panel to bars with color fill
for better readability. Removed options for lines, stepped lines, and
points.

Ref issue: #7101 

<img
src="https://github.com/user-attachments/assets/62522c97-7e5e-426a-b597-8457b2360f7e"
width="400"/>
2025-01-20 11:34:50 +01:00
Zakhar Bessarab
54ab08d839 docs/release-guide: update helm release guide (#8090)
### Describe Your Changes

Release procedure was updated on helm charts side, update documented
procedure with up-to-date steps for the release.

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-20 11:32:14 +01:00
Zakhar Bessarab
a3ea6d9e61 {docs,deployment}: use explicit version tag instead of "latest" (#8089)
### Describe Your Changes

Using latest leads to inconsistent results between runs and might also
lead to data corruption since "latest" can be updated by any development
build.


### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-20 13:48:08 +04:00
Github Actions
f19c760f4f Automatic update helm docs from VictoriaMetrics/helm-charts@ba82ea6 (#8087)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: AndrewChubatiuk <3162380+AndrewChubatiuk@users.noreply.github.com>
2025-01-20 13:07:10 +04:00
Aliaksandr Valialkin
86e74de9db docs/VictoriaLogs/FAQ.md: add answers on how to detect the most frequently seen logs and how to get field names in the selected logs 2025-01-17 17:31:46 +01:00
Aliaksandr Valialkin
4d4253ee17 docs/VictoriaLogs/FAQ.md: add an answer on how to determine log fields, which occupy the most of the disk space and how to deal with such log fields 2025-01-17 17:18:17 +01:00
Github Actions
8c7b5d22c9 Automatic update helm docs from VictoriaMetrics/helm-charts@139a32b (#8077)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: zekker6 <1367798+zekker6@users.noreply.github.com>
2025-01-17 19:43:16 +04:00
Zakhar Bessarab
513f5da5de docs: update reference to the latest release - v1.109.1
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-17 19:42:36 +04:00
Zakhar Bessarab
fb4d545555 deployment/docker: update reference to the latest release - v1.109.1 (#8078)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-17 19:41:28 +04:00
hagen1778
abaf8574a8 docs: mention v1.109.1 as recommendation to update
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-17 15:16:11 +01:00
hagen1778
f346b5aaaa docs: re-purpose b26a68641c as bugfix
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-17 15:15:10 +01:00
hagen1778
31398cc739 docs: move change 4574958e2e to #tip section
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-17 15:14:26 +01:00
Hui Wang
4574958e2e vmselect: add -search.maxDeleteDuration to limit the duration of th… (#8039)
…e `/api/v1/admin/tsdb/delete_series` call

Previously, it is limited by `-search.maxQueryDuration`, and can be
small for delete calls.

part of https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7857.
2025-01-17 15:09:42 +01:00
Konstantin Shalygin
d623105ef4 vmui: web: fixed favicon name (#7632)
### Describe Your Changes

Seems `favicon-32x32.png` removed in #6972

Fixes this:

```shell
√ build % go build -x -buildmode="pie" -trimpath -mod="readonly" -modcacherw -ldflags "-linkmode external"
WORK=/var/folders/mj/r83hky151tzbqsvml0hxts_w0000gn/T/go-build2937230855
main.go:13:12: pattern favicon-32x32.png: no matching files found
```

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-17 15:08:10 +01:00
Daria Karavaieva
aac5cd8574 docs/vmanomaly: fix column width (#8059)
### Describe Your Changes

Fixing column widht in tables for components`sections: reader, writer,
monitoring and scheduler

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-17 15:04:28 +01:00
Github Actions
d3c02b8f5d Automatic update operator docs from VictoriaMetrics/operator@e9d1493 (#8074)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2025-01-17 15:04:09 +01:00
Roman Khavronenko
7f252c1800 fix testsapp/vlselect/logsql: follow-up after 2eb15cf30c964a9ad86f296… (#8075)
…9cbb1518e71fba36b

This fixes tests failing
https://github.com/VictoriaMetrics/VictoriaMetrics/actions/runs/12827319722/job/35769125733?pr=8073#step:5:197

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-17 14:05:53 +01:00
Zakhar Bessarab
f73b40619a docs/changelog/CHANGELOG.md: cut v1.109.1
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-17 14:36:55 +04:00
Aliaksandr Valialkin
0f7b853a88 deployment/docker: update VictoriaLogs Docker image from v1.6.0-victorialogs to v1.6.1-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.6.1-victorialogs
2025-01-16 20:57:49 +01:00
Aliaksandr Valialkin
70f0a974b8 docs/VictoriaLogs/CHANGELOG.md: cut v1.6.1-victorialogs 2025-01-16 20:51:54 +01:00
Aliaksandr Valialkin
2eb15cf30c lib/logstorage: merge top-level _stream:{...} filters in the query
This should improve performance of queries, which contain multiple top-level _stream:{...} filters.

This should help the case described at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8037#issuecomment-2595854592
2025-01-16 20:46:53 +01:00
Aliaksandr Valialkin
499f0b9588 lib/logstorage: add a test for union pipe
This is a follow-up for f27e120aeb
2025-01-16 20:30:28 +01:00
Aliaksandr Valialkin
43d615ae87 lib/logstorage: properly pass tenantIDs list to initStreamFilters
Previously an empty tenantIDs list was mistakenly passed to initStreamFilters
when the query already contained top-level stream filter.

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8037
2025-01-16 17:45:49 +01:00
Zakhar Bessarab
82e1c6fc3f lib/license/online: remove delay before failing if all online verification attempts have failed (#816)
Enable components to fail faster in case all verification attempts have failed. Currently, there will be a final sleep before returning an error.

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-16 20:39:10 +04:00
Mathias Palmersheim
45bfe1f44c added retention filters to Victorialogs roadmap (#8020)
### Describe Your Changes

Fixes #8019 and adds retention filters to victorialogs roadmap as an
enterprise feature

### Checklist

The following checks are **mandatory**:

- [X] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-16 06:59:14 -08:00
Aliaksandr Valialkin
58d2c18423 go.mod: stick cloud.google.com/go/storage to v1.43.0 until https://github.com/googleapis/google-cloud-go/issues/11448 is fixed
This returns back vmbackup and vmrestore binary sizes from 60MB to 40MB.

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8008
See https://github.com/googleapis/google-cloud-go/issues/11448

Thanks to @f41gh7 for the investigation of the issue.
2025-01-16 15:19:42 +01:00
hagen1778
feeda42560 docs: add changelog line for 7d2a6764e7
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-16 15:06:53 +01:00
Roman Khavronenko
7d2a6764e7 app/vmselect/promql: limit staleness detection for increase/increase_pure/delta (#8052)
`doInternal` has adaptive staleness detection mechanism. It is
calculated using timestamp distance between samples in selected list of
samples. It is dynamic because VM can store signals from many sources
with different samples resolution. And while it works for most of cases,
there are edge cases for rollup functions that are comparing values
between windows: increase, increase_pure, delta.

The edge case 1.
There was a gap between series because of the missed scrape or two. In
this case staleness will trigger and increase-like functions will assume
the value they need to compare with is 0. In result, this could produce
spikes for a flappy data - see
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/894 This
problem was solved by introducing a `realPrevValue` field -
1f19c167a4.
It stores the closest real sample value on selected interval and is used
for comparison when samples start after the gap.

The edge case 2.
`realPrevValue` doesn't respect staleness interval. In result, even if
gap between samples is huge (hours), the increase-like functions will
not consider it as a new series that started from 0. See
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8002.

Covering both edge cases is tricky, because `realPrevValue` has to
respect and not respect the staleness interval in the same time. In
other words, it should be able to ignore periodic missing gaps, but
reset if the gap is too big. While "too big gap" can't be figured out
empirically, I suggest using `-search.maxStalenessInterval` for this
purpose. If `-search.maxStalenessInterval` is set to 0 (default), then
`realPrevValue` ignores staleness interval. If
`-search.maxStalenessInterval` is > 0, then `realPrevValue` respects it
as a staleness interval.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-16 15:01:17 +01:00
Aliaksandr Valialkin
1645542a8a docs/VictoriaLogs/LogsQL.md: use top pipe in examples instead of stats by (...) count() | sort (...) limit N
`top` pipe is shorter and easier to understand
2025-01-16 04:22:18 +01:00
Aliaksandr Valialkin
151eb1e4b6 docs/VictoriaLogs/logsql-examples.md: replace last pipe with first pipe, since it is easier to understand 2025-01-16 04:21:09 +01:00
Aliaksandr Valialkin
5e4de8e860 docs/VictoriaLogs/LogsQL.md: use proper backticks around hexnumencode: 2025-01-16 04:12:37 +01:00
Aliaksandr Valialkin
6312d3bbba docs/VictoriaLogs: typo fixes for block_stats pipe docs 2025-01-16 03:53:50 +01:00
Aliaksandr Valialkin
d2bede6b51 docs/VictoriaLogs/LogsQL.md: add missing they word 2025-01-16 03:40:42 +01:00
Aliaksandr Valialkin
5ca5069fc4 docs/VictoriaLogs/LogsQL.md: fix incorrect url to VictoriaMetrics histogram buckets
This is a follow-up for d2a791bef3
2025-01-16 00:02:51 +01:00
Aliaksandr Valialkin
8a3c460f63 deployment/docker: update VictoriaLogs Docker images from v1.5.0-victorialogs to v1.6.0-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.6.0-victorialogs
2025-01-15 22:37:24 +01:00
Aliaksandr Valialkin
ca653a515c docs/VictoriaLogs/CHANGELOG.md: cut v1.6.0-victorialogs release 2025-01-15 22:31:13 +01:00
Aliaksandr Valialkin
e5b4cf33bf lib/logstorage: make golangci-lint happy after f27e120aeb 2025-01-15 22:28:13 +01:00
Aliaksandr Valialkin
e24a8f2088 deployment/docker: update Alpine Docker image from 3.21.0 to 3.21.2
See https://alpinelinux.org/posts/Alpine-3.21.1-released.html and https://alpinelinux.org/posts/Alpine-3.18.11-3.19.6-3.20.5-3.21.2-released.html
2025-01-15 22:26:26 +01:00
Aliaksandr Valialkin
f27e120aeb lib/logstorage: add union pipe, which allows uniting results from multiple queries 2025-01-15 22:22:07 +01:00
Aliaksandr Valialkin
ee1ce90501 lib/logstorage: properly drop temporary directories created by filter* tests 2025-01-15 22:22:07 +01:00
Aliaksandr Valialkin
47fe8cf3be lib/logstorage: math pipe: add rand() function 2025-01-15 22:22:06 +01:00
Daria Karavaieva
5813aa6602 docs/vmanomaly: fix parameters field width (#8025)
### Describe Your Changes

- fix parameters width in Components section - reader, wtiter,
monitoring, scheduler

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-15 20:17:53 +01:00
Aliaksandr Valialkin
b4f4ece162 lib/logstorage: improve performance of unique pipe for integer columns with big number of unique values 2025-01-15 19:53:10 +01:00
Aliaksandr Valialkin
bb00f7529f lib/logstorage: improve performance when applying math calculations for _time, const and dict values 2025-01-15 19:53:10 +01:00
Roman Khavronenko
ad3bd11334 docs: mention regression in v1.109.0 (#8046)
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8045

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-15 16:26:24 +01:00
Github Actions
875c6663ef Automatic update helm docs from VictoriaMetrics/helm-charts@d8e5f03 (#8035)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: AndrewChubatiuk <3162380+AndrewChubatiuk@users.noreply.github.com>
2025-01-15 15:16:02 +08:00
Zakhar Bessarab
b48b7c454a docs: update references to v1.109.0 (#8034)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:42:11 +01:00
Zakhar Bessarab
f523348b3f deployment/docker: update to v1.109.0 (#8032)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:39:06 +01:00
Zakhar Bessarab
63bf1e008f docs/changelog: add missing entry for #7182 (#8030)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:30:26 +01:00
Zakhar Bessarab
419ac10c60 docs/lts-releases: update links to latest releases (#8028)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:22:29 +01:00
Dmytro Kozlov
d631d2c100 cloud: update email images for cloud (#8024)
### Describe Your Changes

Updated email images with new support email

### Checklist

The following checks are **mandatory**:

- [X] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-14 14:24:33 +01:00
Zakhar Bessarab
89431458bf docs/release-guide: move testing on sandbox step before pushing tags (#8026)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 14:23:32 +01:00
Zakhar Bessarab
d8d0c0ac01 docs/changelog: port LTS changes, update release date (#8027)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2025-01-14 14:23:01 +01:00
Roman Khavronenko
c0f5699bad docs: cut new changelog doc for 2024 (#8023)
Cutting new changelod doc reduces the size of the current's year
changelog and improves navigation for users.

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-14 11:31:16 +01:00
Nikolay
277fdd1070 lib/storage: reduce test suite batch size (#8022)
Commit eef6943084 added new test
functions. Which checks various cases for metricName registration at
data ingestion.
Initial dataset size had 4 batches with 100 rows each. It works fine at
machines with 5GB+ memory.
But i386 architecture supports only 4GB of memory per process.

Due to given limitations, batch size should be reduced to 3 batches and
30 rows. It keeps the same
test funtionality, but reduces overall memory usage to ~3GB.

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-14 11:27:50 +01:00
Roman Khavronenko
d290efb849 lib/opentlemetry: throttle log messages during parsing (#8021)
Samples parsing is a hot path. Bad client could easily overwhelm
receiver with bad or unsupported data. So it is better to throttle such
messages.

Follow-up after
b26a68641c

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-14 11:03:11 +01:00
chenlujjj
b26a68641c lib/opentelemetry: log the metric name of unsupported metrics (#8018)
To resolve:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8009
Log the name of unsupported metrics.
2025-01-14 10:49:30 +01:00
Aliaksandr Valialkin
b88cda5c41 lib/logstorage: make golangci-lint happy after the commit d2a791bef3 2025-01-13 22:31:33 +01:00
Aliaksandr Valialkin
d2a791bef3 lib/logstorage: add histogram stats function for calculating histogram buckets over numeric fields 2025-01-13 22:30:19 +01:00
Aliaksandr Valialkin
99516a5730 lib/logstorage: top pipe: allow mixing the order of hits and rank suffixes 2025-01-13 22:30:19 +01:00
Aliaksandr Valialkin
aecc86c390 lib/logstorage: do not copy pipeTopkProcessorShard when obtaining parition keys 2025-01-13 22:30:19 +01:00
Aliaksandr Valialkin
500b54f5aa app/vlogscli: typo fix, which could result in incomplete results in compact mode 2025-01-13 22:30:18 +01:00
Aliaksandr Valialkin
cc29692e27 lib/logstorage: track integer field values in integer map for top N (int_field)
This reduces memory usage by up to 2x for the map used for tracking hits.
This also reduces CPU usage for tracking integer fields.
2025-01-13 22:30:18 +01:00
Aliaksandr Valialkin
f018aa33cb lib/logstorage: avoid callback overhead at visitValuesReadonly
Process values in batches instead of passing every value in the callback.
This improves performance of reading the encoded values from storage by up to 50%.
2025-01-13 22:30:17 +01:00
Daria Karavaieva
92b6475fa6 docs/vmanomaly: fix table rendering (#8005)
### Describe Your Changes

- fix table rendering on writer and scheduler pages

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-13 14:19:16 +01:00
Andrii Chubatiuk
bda3546cfd docs: added search resupts page for docs.victoriametrics.com (#8017)
### Describe Your Changes

added search page required for docs site

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-13 14:18:25 +01:00
Artem Navoiev
2691cdefe3 docs: cloud use separate support email for cloud (#8013)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Artem Navoiev <tenmozes@gmail.com>
2025-01-13 14:17:38 +01:00
Github Actions
93b8aa5c9d Automatic update helm docs from VictoriaMetrics/helm-charts@457951a (#8014)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2025-01-13 12:12:48 +01:00
Aliaksandr Valialkin
7a7f188133 deployment/docker: update VictoriaLogs Docker image tag from v1.4.0-victorialogs to v1.5.0-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.5.0-victorialogs
2025-01-13 07:34:33 +01:00
Aliaksandr Valialkin
3e00fae3f4 docs/VictoriaLogs/CHANGELOG.md: cut v1.5.0-victorialogs release 2025-01-13 07:28:08 +01:00
Roman Khavronenko
ee3c0c6a87 make: bump golangci-lint to v1.63.4 (
New version has additional checks and reduced resource consumption, so
it doesn't timeout for our internal repos.

To make linter happy, I addressed "redefinition of the built-in
function" lint error.

----
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-13 07:18:04 +01:00
Aliaksandr Valialkin
cf7ea78588 lib/logstorage: format pipe: add frequently used formatters
- url encoding / decoding with <urlencode:field> and <urldecode:field>
- base64 encoding / decoding with <base64encode:field> and <base64decode:field>
- hex encoding / decoding with <hexencode:field> and <hexdecode:field>
- hex encoding for integers with <hexnumencode:field> and <hexnumdecode:field>
2025-01-13 07:08:43 +01:00
Aliaksandr Valialkin
186aa3bb0e lib/logstorage: explicitly pass statsFunc to statsProcessor methods
This allows reducing the state of every statsProcessor by removing pointer to the corresponding statsFunc.
For example, this reduces statsCountProcessor size by 2x.
2025-01-13 04:49:39 +01:00
Aliaksandr Valialkin
e368f687a7 lib/logstorage: stats pipe: stop finalizeStats() as soon as the query is canceled
Previoysly finalizeStats() for some functions such as count_uniq() could run for long periods
of time after the query is canceled, since stopCh wan't propagated to finalizeStats().
2025-01-13 03:38:09 +01:00
Aliaksandr Valialkin
0214aa328e lib/logstorage: stats pipe: use integer group keys if stats by(...) contains a single field with integer values
This reduces memory usage and improves performance, since access to a map with integer keys
is faster than access to a map with string keys.
2025-01-13 03:22:00 +01:00
Aliaksandr Valialkin
dd919eeee6 lib/logstorage: count_uniq and count_uniq_hash stats functions: avoid converting integer values to strings
Prevsiously integer values were converted to strings before being passed to `updateState()` function at `count_uniq`
and `count_uniq_hash`. Later such values are converted back to integers in order to track them via integer map of unique values.

This commit avoids the int -> string -> int conversion. Instead, it passes integers directly to the integer map of unique values.
This improves performance of `count_uniq` and `count_uniq_hash` functions even further.
2025-01-13 02:45:28 +01:00
Aliaksandr Valialkin
3f22d06b0c lib/logstorage: add value_type filter to LogsQL
This filter can be used when debugging and exploring logs in order to understand better
which value types are used for storing the particular log fields.

The `value_type` filter complements `block_stats` pipe.
2025-01-12 22:21:39 +01:00
Aliaksandr Valialkin
b812de236b lib/logstorage: run make fmt after e610edf045 2025-01-12 03:17:57 +01:00
Aliaksandr Valialkin
40f56fa93b vendor: run make vendor-update
Add exclude google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a to go.mod
according to https://github.com/googleapis/google-cloud-go/issues/11283#issuecomment-2558515586 .

This fixes the following strange issue on `make vendor-update`:

cloud.google.com/go/storage imports
        google.golang.org/grpc/stats/opentelemetry: ambiguous import: found package google.golang.org/grpc/stats/opentelemetry in multiple modules:
        google.golang.org/grpc v1.69.0 (/go/pkg/mod/google.golang.org/grpc@v1.69.0/stats/opentelemetry)
        google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a (/go/pkg/mod/google.golang.org/grpc/stats/opentelemetry@v0.0.0-20240907200651-3ffb98b2c93a)
2025-01-12 03:14:52 +01:00
Aliaksandr Valialkin
e610edf045 lib/logstorage: improve performance for math pipe
- Pass the calculated results to the next pipe in float64 columns.
  Previously the results were converted to string columns. This could slow down further calculations.

- Use custom optimized logic for processing numeric columns, which are passed to math pipe.
  Previously all the input columns were converted to string and then converted to float64
  before math pipe calculations.

- Initialize the newly added columns at blockResult as soon as they are added.
  This improves performance when big number of columns are calculated by math pipe.
2025-01-12 03:01:47 +01:00
Aliaksandr Valialkin
764955b61c lib/logstorage: track integer values in integer maps when counting the number of unique values at count_uniq stats function
Previously integer values were tracked in string maps. Now every input value is parsed as integer.
On success the parsed integer is tracked via specialized maps, which hold only integers.
This reduces CPU usage and memory usage in general case.
2025-01-12 03:01:46 +01:00
Aliaksandr Valialkin
e3d31a371a lib/logstorage: avoid copying column name inside blockSearch.getColumnHeader() and blockSearch.getConstColumnValue()
Use the column name attached to the corresponding part. The lifetime of this column name exceed the blockSearch lifetime,
so it is safe using it here.

This is a follow-up for 8d968acd0a
2025-01-12 03:01:46 +01:00
Aliaksandr Valialkin
df723a4870 lib/logstorage: automatically detect columns with int64 values and store them as packed 8-byte int64 values
Previously columns with negative int64 values were stored either as float64 or string
depending on whether the negative int64 values are bigger or smaller than -2^53.
If the integer values are smaller than -2^53, then they are stored as string, since float64 cannot
hold such values without precision loss. Now such values are stored as int64.
This should improve compression ratio and query performance over columns with negative int64 values.
2025-01-12 03:01:46 +01:00
Aliaksandr Valialkin
bd00e3a735 lib/logstorage: make sure that the automatic conversion of field values to float64 is lossless
Previously field values could be automatically converted to float64 with precision loss.
This could lead to unexpected results when querying such field values.
For example, "10007199254740992" was incorrectly represented as 10007199254740993.
This commit prevents from such lossy conversions when storing field values.

While at it, prevent from int64 overflow at tryParseBytes and tryParseDuration functions,
which are used for parsing constants in queries for byte sizes and durations.
Now these functions return 1<<63-1 (the maximum int64 value) for constants exceeding
this value. Previously they could return arbitrary garbage for such constants.
2025-01-12 03:01:45 +01:00
Aliaksandr Valialkin
e794582f31 app/vlinsert/insertutils: avoid excess copying of lines at LineReader.buf
1. Do not copy every line from LineReader.buf to LineReader.Line - just refer the line at LineReader.buf.
2. Do not copy the next found line to the beginning of LineReader.buf - just track the next line start index with LineReader.bufOffset.

This reduces memory copying when many lines are read into LineReader.buf by a single read() syscall.
2025-01-12 03:01:45 +01:00
Roman Khavronenko
7cab4fd30d app/vmselect/promql: account for staleness when populating realPrevValue (#8002)
When vmselect process a rollup function it fetches all the raw samples
on requested `start-end` interval of the query. It then loops through
the raw samples, picks the range of the samples based on provided `step`
interval and invokes a rollup function for each of the picked ranges of
samples.

During this processing, vmselect always populates the `realPrevValue`
field with the closest previous raw sample value before the picked range
of samples. This `realPrevValue` is used by rollup functions like
increase_pure or delta to decide whether the counter change happened or
not. For example, we get the counter value == 1. If we've seen this
counter before and its value was also 1 - then no change happened. If we
didn't see it before, then this counter should have started with value=0
and we need to account for `1-0=1` change. All this is required to deal
with situations when scrapes are missing or `step` is too small.

However, vmselect doesn't check how "old" is the `realPrevValue`. In
other words, it doesn't respect the staleness interval when picking it.
In result, depending on the `start` and `end` params, vmselect can use
`realPrevValue` which is a couple of hours old and is unlikely to be a
temporary scrape fail. In result, some increases can be incorrectly
ingnored by vmselect.

This change makes sure that vmselect doesn't populate `realPrevValue`
with samples that are older than staleness interval.

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ x ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).


-------------------

To reproduce, create a dataset with one metric `foo` which has samples
with value=1 on interval of couple of hours and resolution 15s, and a
gap for an hour in the middle:
<img width="769" alt="image"
src="https://github.com/user-attachments/assets/a39b2740-b741-45f8-ad18-093b7c57c3b3"
/>

Then run `increase(foo[1m])` expression on this time range (disable
cache):
<img width="1472" alt="image"
src="https://github.com/user-attachments/assets/463cece1-f359-4c75-a96c-60092a31cab2"
/>

In result, there will be one increase on the beginning of the series.
And no increase after the gap. Then change the time range so it starts
in the middle of the gap:
<img width="1505" alt="image"
src="https://github.com/user-attachments/assets/f4a460c3-9fd1-4ec7-ab47-15e716ec1019"
/>

Now, there is an increase>0 because the `realPrevValue` wasn't
populated. This is wrong, because it hides the increase of the series.

With the fix, the original increase query on full time range should show
2 increases:
<img width="1492" alt="image"
src="https://github.com/user-attachments/assets/aa9d8a6b-7b22-41f6-9eb9-83b3113a6982"
/>

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-10 16:45:44 +01:00
Zakhar Bessarab
3333135bc0 docs/CHANGELOG.md: cut v1.109.0
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-10 18:53:33 +04:00
Zakhar Bessarab
1db1841b20 app/{vmselect,vlselect}: run make vmui-update vmui-logs-update
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-10 18:53:33 +04:00
Aliaksandr Valialkin
f7ce191482 docs/VictoriaLogs/README.md: add "Profiling" chapter
This chapter is needed for referring from Github issues when CPU or memory profiles are needed to be collected
in order to investigate issues with high CPU and/or RAM usage at VictoriaLogs.
2025-01-10 14:22:08 +01:00
Aliaksandr Valialkin
96ea222780 LICENSE: update the current year from 2024 to 2025 2025-01-10 14:19:11 +01:00
Zakhar Bessarab
03c0d9a672 app/vmselect/promql: set tenant information for numbers
Since
44b071296d
`evalNumber` function no longer updating MetricName tenancy information.
This leads to mismatch in metric names between the query result and
evaluated number for all tenants other than 0:0.

For example, query `count(up) or 0` will return different results for
tenants 0:0 and 1:1 (assuming up is present for both tenants):
- tenant 0:0 - will only contain result of `count(up)`
- tenant 1:1 - will return both `count(up)` and `0` since metric names
will not be matched

This restores setting of tenancy information for metric name for
single-tenant queries.

Related issue:

 https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7987
---
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-10 13:04:38 +01:00
Nikolay
e9f86af7f5 lib/storage: add a hint for merge about type of parts in merge (#7998)
Hint allows to choose type of cache to be used for index search:
- in-memory parts are storing recently ingested samples and should use
main cache. This improves ingestion speed and cache hit ration for
queries accessing recently ingested samples.
- merges of file parts is performed in background, using a separate
cache allows avoiding pollution of the main cache with irrelevant
entries.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7182

---------

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-10 16:01:39 +04:00
Nikolay
9ada784983 lib/storage: make finalDedup schedule interval configurable
This commit makes configurable interval for checking if final dedup
process for the historical data should be started. It allows to spread
resource utilisation for multiple vmstorage/vmsingle instances in time.
Since final dedup may add additional preasure on disk, backup systems
and make cluster less stable. Storage unconditionally adds 25% jitter to
the provided value, it should simplify configuration management at
Kubernetes ecosystem. Because Kubernetes application pods must have the
same configuration.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7880


---------

Signed-off-by: f41gh7 <nik@victoriametrics.com>
Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2025-01-10 10:46:46 +01:00
f41gh7
a83ee2b3f1 github/workflows: set GOGC=10 for unit tests
It reduces memory usage during tests execution. It makes tests execution more reliable.
Since it sometimes crashes with OOM at small github runners.

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-10 10:45:41 +01:00
Github Actions
2564f10d98 Automatic update helm docs from VictoriaMetrics/helm-charts@117506e (#7996)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: AndrewChubatiuk <3162380+AndrewChubatiuk@users.noreply.github.com>
2025-01-10 05:16:54 +01:00
Andrii Chubatiuk
0871770634 victorialogs: ugraded datadog extension version in compose to one which supports custom endpoint configuration (#7989)
recently new datadog extension was released, where custom endpoint
configuration was added

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-09 10:44:36 +04:00
Zakhar Bessarab
51b21dfd57 app/vmalert/notifier: fix rendering of Alertmanager notification body
commitL  c7fc0d0d2f  enabled skipping alerts
in case there is no labels present for an alert. This made clause which
was adding a comma for the JSON list incorrect as it is not possible to
determine if the next alert will be skipped or not.

This fix renders all alert labels in advance allowing properly format
JSON payload for Alertmanager notification.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7985


Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-08 19:02:20 +01:00
Zhu Jiekun
276989716f lib/promscrape: add Marathon service discovery
This commit adds support for [Marathon](https://mesosphere.github.io/marathon/)
service discovery to the scrape configuration. 

The following flag is introduced:
```
  -promscrape.marathonSDCheckInterval duration
          Interval for checking for changes in Marathon service discovery. This works only if marathon_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs.html#marathon_sd_configs for details  (default 30s)
```

The service discovery could be config like:
```yaml
scrape_configs:          
- job_name: marathon_job 
  marathon_sd_configs:   
      servers:
        - "..."
        - "..."
```
See:
[b555d94d1a/docs/sd_configs.md (marathon_sd_configs))

related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6642


---------

Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-08 18:57:22 +01:00
Artem Fetishev
6cb3c0cac8 Update VictoriaLogs FAQ: add a section about max log record length (#7984)
### Describe Your Changes

There has been a question in our public Slack on whether the length
limit of a log record is going to be changed. See:
https://victoriametrics.slack.com/archives/C05UNTPAEDN/p1736156255119689

This PR documents the max length and explains why it has been chosen.
This FAQ section could serve as an answer to more questions like this.

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2025-01-08 15:33:38 +01:00
cuiweiyuan
d064e14933 chore: fix function name in comment (#7926)
### Describe Your Changes

 fix function name in comment

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: cuiweiyuan <cuiweiyuan@aliyun.com>
2025-01-08 13:58:22 +01:00
Afolabi Badmos
77b0fcfdd9 vmauth: fix bug in discovering ipv6 addresses (#7955)
### Describe Your Changes

Fixes error in `vmauth` when discovering ipv6 addresses.

`vmauth` attempts to [slice till
`:`](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmauth/auth_config.go#L397)
in the discovered addresses without accounting for ipv6. This causes it
to fail in ipv6 only environments.

```sh
$ nslookup vmselect.ns.svc.cluster.local

...
Name: vmselect.ns.svc.cluster.local
Address: 2600:dead:beef:dead:beef::8
```

```sh
$ kubectl logs -f vmauth

...
error: dial tcp: lookup 2600: no such host
```


### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Co-authored-by: f41gh7 <nik@victoriametrics.com>
2025-01-08 16:51:13 +04:00
Yury Molodov
ee7fe11fd2 vmui/logs: add autocomplete support for LogsQL (#6949)
### Describe Your Changes

This pull request adds support for autocomplete in LogsQL queries. The
new feature provides suggestions for field names, field values, and pipe
names as you type.


---------

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2025-01-08 11:36:37 +01:00
Andrii Chubatiuk
4c26fb6fe5 docs: make badges in docs clickable (#7960)
### Describe Your Changes

added links to badges and made them clickable at
docs.victoriametrics.com

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-08 08:58:43 +01:00
Github Actions
fc135094b3 Automatic update operator docs from VictoriaMetrics/operator@028ab8b (#7981)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2025-01-08 07:45:54 +01:00
Roman Khavronenko
5d42f21abd docs: mention publicly available playgrounds (#7977)
The point of the new section is to highlight publicly available
playgrounds for users. All of them were mentioned in other parts of the
documentation, but we didn't have all of them in one place before.

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 22:16:50 +01:00
hagen1778
28eeabded1 docs: rm extra new lines as they bring no value
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 13:52:35 +01:00
hagen1778
b6910cfff7 docs: make vmui related pages below the vmui parent page
This change only updates the hierarchy of pages within the readme.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 13:48:39 +01:00
Zhu Jiekun
8938ef398c docs: clarify extra resource is needed for downsampling/retention filter (#7974)
### Describe Your Changes

clarify extra resource is needed when downsampling with filter(s) or
retention filter(s) is applied

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-07 12:30:19 +01:00
hagen1778
df2b75fa81 docs: fix markdown typos
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 09:18:25 +01:00
hagen1778
857734c66c docs: fix markdown typo
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 09:06:45 +01:00
Github Actions
bedc0c0f8f Automatic update helm docs from VictoriaMetrics/helm-charts@7004727 (#7958)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: AndrewChubatiuk <3162380+AndrewChubatiuk@users.noreply.github.com>
2025-01-03 19:06:18 +04:00
YuDong Tang
5a41bdf329 app/select: add command-line flag -search.maxBinaryOpPushdownLabelValues
### Describe Your Changes

Binary operations like `exprFirst op exprSecond` in VictoriaMetrics are
performed in the following way:
1. Execute exprFirst.
2. Extract **common label filters** from the result of step 1.
3. Apply these common label filters to `exprSecond` and execute it, in
order to retrieve less time series from vmstorage nodes.

In step 2, only labels with less than `100` (hard-coded) value could be
used as **common label filter** (e.g. `{common_lb=~"v1|v2|...|v100"}`.

In our scenarios, a label, take `instance` label as an example, could
has thousands of candidate values. Regarding bring more pressure to
vmstorage node, it's still beneficial if labels with more than 100
values could be used as filter in `exprSecond`, with enough vmstorage
resources. After adjusting the value from `100` to `10000`, our query
round-trip time drops significantly from 5s to 2s.

This pull request change the hard-coded value into a configurable flag.
2025-01-03 13:20:50 +01:00
Github Actions
bf5d0dd245 Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@7f94969 (#7911) 2025-01-03 14:58:46 +04:00
Github Actions
1cec37b0f5 Automatic update operator docs from VictoriaMetrics/operator@5992757 (#7954)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2025-01-02 18:46:11 +01:00
f41gh7
c40c25b03c docs/changelog: properly mention vminsert changes
storageNode sorting should be BUGFIX, since previously vminsert performed sort and this behaviour was changed.
Also this change only affects OSS version
2025-01-02 17:53:57 +01:00
kiriklo
82badc3dd5 app/vmselect/promql: improve performance of parseCache on systems with many CPU cores
### Describe Your Changes

Parse cache is a pretty simple implementation of cache. It's just a
standard map with mutex.
Map with mutex overall has poor performance, plus when the cache
overflow occurs, the whole cache locks until 1k elements have been
deleted (now it's 10% of 10000 max elements in the cache). To avoid this
bottleneck and improve performance of cache on systems with many CPU
cores but keep it rather simple, we can implement cache with per bucket
locks like it's done in fastcache. The logic and API remain the same. So
now each bucket will have a map with approximately 78 elements (with 128
buckets), and overflow will occur now for each bucket, and only 7
elements need to be deleted.
Because exec_test.go has about 10k lines of code, it's better to move
the cache into a separate file to add tests and benchmarks for it,
because now it does not have them.

```
goos: windows
goarch: amd64
pkg: github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql
cpu: 11th Gen Intel(R) Core(TM) i9-11900K @ 3.50GHz

Current cache implementation performance on 8 cores:
BenchmarkCachePutNoOverFlow-8               1932            618372 ns/op             253 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-8               6547            211527 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-8            1873            621718 ns/op             261 B/op          0 allocs/op
BenchmarkCachePutOverflow-8                 2262            464328 ns/op              32 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-8              1764            655866 ns/op              38 B/op          0 allocs/op

New cache implementation performance on 8 cores:
BenchmarkCachePutNoOverFlow-8              10408            111412 ns/op               0 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-8              22407             52809 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-8            6583            168088 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutOverflow-8                 9822            117212 ns/op               2 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-8              6481            175952 ns/op               3 B/op          0 allocs/op

Current cache implementation performance on 16 cores:
BenchmarkCachePutNoOverFlow-16              2331            475307 ns/op             218 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-16              6069            196905 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-16           1870            644236 ns/op             262 B/op          0 allocs/op
BenchmarkCachePutOverflow-16                2296            509279 ns/op              34 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-16             1726            671510 ns/op              45 B/op          0 allocs/op

New cache implementation performance on 16 cores:
BenchmarkCachePutNoOverFlow-16             13549             82413 ns/op               0 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-16             30274             38997 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-16           8512            126239 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutOverflow-16               13884             88124 ns/op               1 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-16             7903            131299 ns/op               3 B/op          0 allocs/op
```
From the benchmarks above, we can see that the new implementation is ~5
times faster than the old one.


---------
Co-authored-by: f41gh7 <nik@victoriametrics.com>
2025-01-02 17:43:23 +01:00
Alex Gustafsson
43ded688f7 Add open containers source label to Dockerfiles (#7893)
### Describe Your Changes

In order for third-party tooling to identify the source repository of
VictoriaMetrics, add the org.opencontainers.image label to the
Dockerfiles. This enables a whole suite of tools that scan container
images to further correlate data with the source code.

The lack of these annotations can be identified using docker:

```shell
docker pull victoriametrics/victoria-metrics
docker inspect victoriametrics/victoria-metrics
```

```jsonc
// ...
"Labels": null
// ...
```

If we try an image that has the annotations, we'll see more output.

```shell
docker pull traefik
docker image inspect traefik
```

```jsonc
// ...
"Labels": {
    "org.opencontainers.image.description": "A modern reverse-proxy",
    "org.opencontainers.image.documentation": "https://docs.traefik.io",
    "org.opencontainers.image.source": "https://github.com/traefik/traefik",
    "org.opencontainers.image.title": "Traefik",
    "org.opencontainers.image.url": "https://traefik.io",
    "org.opencontainers.image.vendor": "Traefik Labs",
    "org.opencontainers.image.version": "v3.2.3"
}
// ...
```

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-02 20:43:17 +04:00
Hui Wang
661420fe85 dashboard/vmagent: change metric for Persistent Queue panel
consistently use `vmagent_remotewrite_pending_data_bytes`  on vmagent dashboard to represent persistent queue size.

`vmagent_remotewrite_pending_data_bytes =
vm_persistentqueue_bytes_pending + pendingInmemoryBytes`
According to panel description, `vmagent_remotewrite_pending_data_bytes`
is more accurate.
>Persistent queue size shows size of pending samples in bytes which
hasn't been flushed to remote storage yet.
 
And we already use `vmagent_remotewrite_pending_data_bytes` in other two
panels.

44d2205136/dashboards/vmagent.json (L7132)
2025-01-02 13:04:40 +01:00
Andrii Chubatiuk
7aab967447 Makefile: cspell makefile refactor
- removed absolute paths to run without docker
- set cspell to default entrypoint value
- set cspell config path instead of cspell.json copying and removal
2025-01-02 12:52:24 +01:00
Hui Wang
afb07034ed app/vmalert: fix the auto-generated metrics ALERTS and ALERTS_FOR_STATE
Previously, since labels slice is reused for both `ALERTS` and
`ALERTS_FOR_STATE`, metrics might have incorrect labels and affect the
restore process. Tested the fix under `TestAlertingRule_Exec:
"for-pending=>empty"`.

The bug is introduced in
282f13cf11.
Affected versions: v1.106.1, v1.107...v1.108.x

related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7796
2025-01-02 12:51:05 +01:00
f41gh7
44d2205136 app/vmalert: properly format datasource URL for enterprise group.tenant
At Enterprise version of the vmalert, `group` supports `tenant` field.

`tenant` field value must be added to the `datasource` as a part of the URL path prefix.

But VictoriaLogs can obtain tenant information only from `headers` and defined `tenant` breaks requests to the `VictoriaLogs` datasource.

 This commit properly checks `datasourceType` and skips adding path prefix if `datasourceType` is `vlogs`.

---------
Co-authored-by: Nikolay <nik@victoriametrics.com>
2024-12-30 15:42:29 +01:00
f41gh7
b226318f9e app/vmstorage: allow to override the default unique time series limit
previously vmstorage ignored limit values from vmselect component.

This behavior is prohibited starting from v1.105.0, with
85f60237e2.

This breaks the original intent of the -search.maxUniqueTimeseries command-line flag, which has been added at vmselect nodes in the commit b843f0e : to be able to override the default limit at vmstorage on the number of unique time series, at different subsets of vmselect nodes.

The behavior should be the following:

*    If -search.maxUniqueTimeseries command-line flag isn't set at both vmselect and vmstorage nodes, then the limit on  the number of unique time series must be automatically detected at vmstorage nodes according to

* vmstorage: automatically adjust -search.maxUniqueTimeseries max value   . This simplifies configuration of VictoriaMetrics cluster for the typical case.

* If -search.maxUniqueTimeseries command-line flag is explicitly set at vmstorage node, then it must be used as the limit on the number of unique time series, without automatic detection of the limit. Explicitly set limit at vmstorage node cannot be exceeded by the limit from vmselect nodes.
* If the -search.maxUniqueTimeseries command-line flag is explicitly set at vmselect node, then it must override the automatically detected limit at vmstorage node. For example, if vmselect node provides the limit, which exceeds the automatically detected limit at vmstorage node, then the limit from the vmselect node must be applied during query execution at vmstorage node. This will allow properly executing queries from the subset of vmselect nodes for reporting queries described above.

related issue:
 https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7852
2024-12-30 15:20:52 +01:00
Github Actions
30999204c9 Automatic update operator docs from VictoriaMetrics/operator@5e0854a (#7938)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: AndrewChubatiuk <3162380+AndrewChubatiuk@users.noreply.github.com>
2024-12-30 14:06:27 +01:00
Zhu Jiekun
ffddfa1f94 app/vmctl: properly handle influx series without tags
### Describe Your Changes

Previously, vmctl expect that tag must exist for each measurement, but
it's actually not necessary.


f16a58f14c/app/vmctl/influx/influx.go (L183-L186)

This pull request fix it by removing the check. For influx series
`measurement1_value1{}`, it will be represented as:
```go
Series{
  Measurement: "measurement1",
  Field:       "value1",
  LabelPairs:  []LabelPair{},
  EmptyTags:   []string{},
}
```
and searched by the following query:
```sql
select "value1" from "measurement1"
``` 

 Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7921
2024-12-26 20:39:06 +01:00
f41gh7
fc336bbf20 app/vminsert: properly ingest influx metrics
Commit 71bb9fc0d0 introduced a regression.
If labels are empty and relabeling is not configured, influx ingestion hanlder
performed an earlier exit due to TryPrepareLabels call.
 Due micro-optimisations for this procotol, this check was not valid.
Since it didn't take in account metircName, which added later and skip metrics line.

 This commit removes `TryPrepareLabel` function call from this path and inline it instead.
It properly track empty labels path.

 Adds initial tests implementation for data ingestion protocols.

 Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7933

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-26 12:14:42 +01:00
f41gh7
e0b2c1c4f5 docs/changelog: removes duplicate record for maxIngestionRate feature
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-24 19:40:44 +01:00
Github Actions
5afbee5f6f Automatic update operator docs from VictoriaMetrics/operator@43f5554 (#7923)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: AndrewChubatiuk <3162380+AndrewChubatiuk@users.noreply.github.com>
2024-12-24 19:31:27 +01:00
Github Actions
51459196f9 Automatic update helm docs from VictoriaMetrics/helm-charts@1f33c21 (#7929)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2024-12-24 19:30:43 +01:00
1557 changed files with 37450 additions and 419918 deletions

View File

@@ -85,7 +85,7 @@ jobs:
restore-keys: go-artifacts-${{ runner.os }}-${{ matrix.scenario }}-
- name: Run tests
run: make ${{ matrix.scenario}}
run: GOGC=10 make ${{ matrix.scenario}}
- name: Publish coverage
uses: codecov/codecov-action@v5

View File

@@ -175,7 +175,7 @@
END OF TERMS AND CONDITIONS
Copyright 2019-2024 VictoriaMetrics, Inc.
Copyright 2019-2025 VictoriaMetrics, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -567,7 +567,7 @@ golangci-lint: install-golangci-lint
golangci-lint run
install-golangci-lint:
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.60.3
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.63.4
remove-golangci-lint:
rm -rf `which golangci-lint`

View File

@@ -1,12 +1,14 @@
# VictoriaMetrics
[![Latest Release](https://img.shields.io/github/release/VictoriaMetrics/VictoriaMetrics.svg?style=flat-square)](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
[![Docker Pulls](https://img.shields.io/docker/pulls/victoriametrics/victoria-metrics.svg?maxAge=604800)](https://hub.docker.com/r/victoriametrics/victoria-metrics)
[![Slack](https://img.shields.io/badge/join%20slack-%23victoriametrics-brightgreen.svg)](https://slack.victoriametrics.com/)
[![GitHub license](https://img.shields.io/github/license/VictoriaMetrics/VictoriaMetrics.svg)](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/LICENSE)
[![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/VictoriaMetrics)](https://goreportcard.com/report/github.com/VictoriaMetrics/VictoriaMetrics)
[![Build Status](https://github.com/VictoriaMetrics/VictoriaMetrics/workflows/main/badge.svg)](https://github.com/VictoriaMetrics/VictoriaMetrics/actions)
[![codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/branch/master/graph/badge.svg)](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics)
![Latest Release](https://img.shields.io/github/v/release/VictoriaMetrics/VictoriaMetrics?sort=semver&label=&filter=!*-victorialogs&logo=github&labelColor=gray&color=gray&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics%2Freleases%2Flatest)
![Docker Pulls](https://img.shields.io/docker/pulls/victoriametrics/victoria-metrics?label=&logo=docker&logoColor=white&labelColor=2496ED&color=2496ED&link=https%3A%2F%2Fhub.docker.com%2Fr%2Fvictoriametrics%2Fvictoria-metrics)
![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/VictoriaMetrics?link=https%3A%2F%2Fgoreportcard.com%2Freport%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics)
![Build Status](https://github.com/VictoriaMetrics/VictoriaMetrics/workflows/main/badge.svg?link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics%2Factions)
![codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/branch/master/graph/badge.svg?link=https%3A%2F%2Fcodecov.io%2Fgh%2FVictoriaMetrics%2FVictoriaMetrics)
![License](https://img.shields.io/github/license/VictoriaMetrics/VictoriaMetrics?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
![X](https://img.shields.io/twitter/follow/VictoriaMetrics?style=flat&label=Follow&color=black&logo=x&labelColor=black&link=https%3A%2F%2Fx.com%2FVictoriaMetrics)
![Reddit](https://img.shields.io/reddit/subreddit-subscribers/VictoriaMetrics?style=flat&label=Join&labelColor=red&logoColor=white&logo=reddit&link=https%3A%2F%2Fwww.reddit.com%2Fr%2FVictoriaMetrics)
<picture>
<source srcset="docs/logo_white.webp" media="(prefers-color-scheme: dark)">

View File

@@ -42,6 +42,10 @@ var (
"Smaller intervals increase disk IO load. Minimum supported value is 1s")
maxIngestionRate = flag.Int("maxIngestionRate", 0, "The maximum number of samples vmsingle can receive per second. Data ingestion is paused when the limit is exceeded. "+
"By default there are no limits on samples ingestion rate.")
finalDedupScheduleInterval = flag.Duration("storage.finalDedupScheduleCheckInterval", time.Hour, "The interval for checking when final deduplication process should be started."+
"Storage unconditionally adds 25% jitter to the interval value on each check evaluation."+
" Changing the interval to the bigger values may delay downsampling, deduplication for historical data."+
" See also https://docs.victoriametrics.com/#deduplication")
)
func main() {
@@ -86,6 +90,10 @@ func main() {
startTime := time.Now()
storage.SetDedupInterval(*minScrapeInterval)
storage.SetDataFlushInterval(*inmemoryDataFlushInterval)
if *finalDedupScheduleInterval < time.Hour {
logger.Fatalf("-dedup.finalDedupScheduleCheckInterval cannot be smaller than 1 hour; got %s", *finalDedupScheduleInterval)
}
storage.SetFinalDedupScheduleInterval(*finalDedupScheduleInterval)
vmstorage.Init(promql.ResetRollupResultCacheIfNeeded)
vmselect.Init()
vminsertcommon.StartIngestionRateLimiter(*maxIngestionRate)

View File

@@ -15,6 +15,8 @@ import (
// LineReader reads newline-delimited lines from the underlying reader
type LineReader struct {
// Line contains the next line read after the call to NextLine
//
// The Line contents is valid until the next call to NextLine.
Line []byte
// name is the LineReader name
@@ -26,6 +28,9 @@ type LineReader struct {
// buf is a buffer for reading the next line
buf []byte
// bufOffset is the offset at buf to read the next line from
bufOffset int
// err is the last error when reading data from r
err error
@@ -51,26 +56,27 @@ func NewLineReader(name string, r io.Reader) *LineReader {
// Check for Err in this case.
func (lr *LineReader) NextLine() bool {
for {
if len(lr.buf) == 0 {
if lr.bufOffset >= len(lr.buf) {
if lr.err != nil || lr.eofReached {
return false
}
if !lr.readMoreData() {
return false
}
if len(lr.buf) == 0 && lr.eofReached {
if lr.bufOffset >= len(lr.buf) && lr.eofReached {
return false
}
}
if n := bytes.IndexByte(lr.buf, '\n'); n >= 0 {
lr.Line = append(lr.Line[:0], lr.buf[:n]...)
lr.buf = append(lr.buf[:0], lr.buf[n+1:]...)
buf := lr.buf[lr.bufOffset:]
if n := bytes.IndexByte(buf, '\n'); n >= 0 {
lr.Line = buf[:n]
lr.bufOffset += n + 1
return true
}
if lr.eofReached {
lr.Line = append(lr.Line[:0], lr.buf...)
lr.buf = lr.buf[:0]
lr.Line = buf
lr.bufOffset += len(buf)
return true
}
if !lr.readMoreData() {
@@ -88,6 +94,11 @@ func (lr *LineReader) Err() error {
}
func (lr *LineReader) readMoreData() bool {
if lr.bufOffset > 0 {
lr.buf = append(lr.buf[:0], lr.buf[lr.bufOffset:]...)
lr.bufOffset = 0
}
bufLen := len(lr.buf)
if bufLen >= MaxLineSizeBytes.IntN() {
logger.Warnf("%s: the line length exceeds -insert.maxLineSizeBytes=%d; skipping it; line contents=%q", lr.name, MaxLineSizeBytes.IntN(), lr.buf)

View File

@@ -176,7 +176,7 @@ func writeCompactObject(w io.Writer, fields []logstorage.Field) error {
_, err := fmt.Fprintf(w, "%s\n", fields[0].Value)
return err
}
if len(fields) == 2 && fields[0].Name == "_time" || fields[1].Name == "_time" {
if len(fields) == 2 && (fields[0].Name == "_time" || fields[1].Name == "_time") {
// Write _time\tfieldValue as is
if fields[0].Name == "_time" {
_, err := fmt.Fprintf(w, "%s\t%s\n", fields[0].Value, fields[1].Value)

View File

@@ -45,6 +45,8 @@ var (
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
u64FieldsPerLog = flag.Int("u64FieldsPerLog", 1, "The number of fields with uint64 values to generate per each log entry; "+
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
i64FieldsPerLog = flag.Int("i64FieldsPerLog", 1, "The number of fields with int64 values to generate per each log entry; "+
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
floatFieldsPerLog = flag.Int("floatFieldsPerLog", 1, "The number of fields with float64 values to generate per each log entry; "+
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
ipFieldsPerLog = flag.Int("ipFieldsPerLog", 1, "The number of fields with IPv4 values to generate per each log entry; "+
@@ -254,6 +256,9 @@ func generateLogsAtTimestamp(bw *bufio.Writer, workerID int, ts int64, firstStre
for j := 0; j < *u64FieldsPerLog; j++ {
fmt.Fprintf(bw, `,"u64_%d":"%d"`, j, rand.Uint64())
}
for j := 0; j < *i64FieldsPerLog; j++ {
fmt.Fprintf(bw, `,"i64_%d":"%d"`, j, int64(rand.Uint64()))
}
for j := 0; j < *floatFieldsPerLog; j++ {
fmt.Fprintf(bw, `,"float_%d":"%v"`, j, math.Round(10_000*rand.Float64())/1000)
}

View File

@@ -28,7 +28,7 @@ func TestParseExtraFilters_Success(t *testing.T) {
// LogsQL filter
f(`foobar`, `foobar`)
f(`foo:bar`, `foo:bar`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `(foo:bar or foo:baz) error _time:5m {foo="bar",baz="z"}`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `{foo="bar",baz="z"} (foo:bar or foo:baz) error _time:5m`)
}
func TestParseExtraFilters_Failure(t *testing.T) {
@@ -77,7 +77,7 @@ func TestParseExtraStreamFilters_Success(t *testing.T) {
// LogsQL filter
f(`foobar`, `foobar`)
f(`foo:bar`, `foo:bar`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `(foo:bar or foo:baz) error _time:5m {foo="bar",baz="z"}`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `{foo="bar",baz="z"} (foo:bar or foo:baz) error _time:5m`)
}
func TestParseExtraStreamFilters_Failure(t *testing.T) {

View File

@@ -1,13 +1,12 @@
{
"files": {
"main.css": "./static/css/main.fa83344e.css",
"main.js": "./static/js/main.8ad2bc1f.js",
"main.css": "./static/css/main.3134e778.css",
"main.js": "./static/js/main.82cd6930.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.fa83344e.css",
"static/js/main.8ad2bc1f.js"
"static/css/main.3134e778.css",
"static/js/main.82cd6930.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.8ad2bc1f.js"></script><link href="./static/css/main.fa83344e.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.82cd6930.js"></script><link href="./static/css/main.3134e778.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -160,8 +160,8 @@ func (m *manager) update(ctx context.Context, groupsCfg []config.Group, restore
// it is important to call InterruptEval before the update, because cancel fn
// can be re-assigned during the update.
item.old.InterruptEval()
go func(old *rule.Group, new *rule.Group) {
old.UpdateWith(new)
go func(oldGroup *rule.Group, newGroup *rule.Group) {
oldGroup.UpdateWith(newGroup)
wg.Done()
}(item.old, item.new)
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
)
@@ -69,7 +70,17 @@ func (am *AlertManager) Send(ctx context.Context, alerts []Alert, headers map[st
func (am *AlertManager) send(ctx context.Context, alerts []Alert, headers map[string]string) error {
b := &bytes.Buffer{}
writeamRequest(b, alerts, am.argFunc, am.relabelConfigs)
alertsToSend := alerts[:0]
lblss := make([][]prompbmarshal.Label, 0, len(alerts))
for _, a := range alerts {
lbls := a.applyRelabelingIfNeeded(am.relabelConfigs)
if len(lbls) == 0 {
continue
}
alertsToSend = append(alertsToSend, a)
lblss = append(lblss, lbls)
}
writeamRequest(b, alertsToSend, am.argFunc, lblss)
req, err := http.NewRequest(http.MethodPost, am.addr.String(), b)
if err != nil {

View File

@@ -1,15 +1,14 @@
{% import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
) %}
{% stripspace %}
{% func amRequest(alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) %}
{% func amRequest(alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) %}
[
{% for i, alert := range alerts %}
{% code lbls := alert.applyRelabelingIfNeeded(relabelCfg) %}
{% if len(lbls) == 0 %} {% continue %} {% endif %}
{% code lbls := lblss[i] %}
{
"startsAt":{%q= alert.Start.Format(time.RFC3339Nano) %},
"generatorURL": {%q= generatorURL(alert) %},

View File

@@ -8,7 +8,7 @@ package notifier
import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
)
//line app/vmalert/notifier/alertmanager_request.qtpl:8
@@ -25,122 +25,116 @@ var (
)
//line app/vmalert/notifier/alertmanager_request.qtpl:8
func streamamRequest(qw422016 *qt422016.Writer, alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) {
func streamamRequest(qw422016 *qt422016.Writer, alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) {
//line app/vmalert/notifier/alertmanager_request.qtpl:8
qw422016.N().S(`[`)
//line app/vmalert/notifier/alertmanager_request.qtpl:10
for i, alert := range alerts {
//line app/vmalert/notifier/alertmanager_request.qtpl:11
lbls := alert.applyRelabelingIfNeeded(relabelCfg)
lbls := lblss[i]
//line app/vmalert/notifier/alertmanager_request.qtpl:12
if len(lbls) == 0 {
//line app/vmalert/notifier/alertmanager_request.qtpl:12
continue
//line app/vmalert/notifier/alertmanager_request.qtpl:12
}
//line app/vmalert/notifier/alertmanager_request.qtpl:12
//line app/vmalert/notifier/alertmanager_request.qtpl:11
qw422016.N().S(`{"startsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:14
//line app/vmalert/notifier/alertmanager_request.qtpl:13
qw422016.N().Q(alert.Start.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:14
//line app/vmalert/notifier/alertmanager_request.qtpl:13
qw422016.N().S(`,"generatorURL":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:15
//line app/vmalert/notifier/alertmanager_request.qtpl:14
qw422016.N().Q(generatorURL(alert))
//line app/vmalert/notifier/alertmanager_request.qtpl:15
//line app/vmalert/notifier/alertmanager_request.qtpl:14
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:16
//line app/vmalert/notifier/alertmanager_request.qtpl:15
if !alert.End.IsZero() {
//line app/vmalert/notifier/alertmanager_request.qtpl:16
//line app/vmalert/notifier/alertmanager_request.qtpl:15
qw422016.N().S(`"endsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:17
//line app/vmalert/notifier/alertmanager_request.qtpl:16
qw422016.N().Q(alert.End.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:17
//line app/vmalert/notifier/alertmanager_request.qtpl:16
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:18
//line app/vmalert/notifier/alertmanager_request.qtpl:17
}
//line app/vmalert/notifier/alertmanager_request.qtpl:18
//line app/vmalert/notifier/alertmanager_request.qtpl:17
qw422016.N().S(`"labels": {`)
//line app/vmalert/notifier/alertmanager_request.qtpl:20
//line app/vmalert/notifier/alertmanager_request.qtpl:19
ll := len(lbls)
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:20
for idx, l := range lbls {
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().Q(l.Name)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().S(`:`)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().Q(l.Value)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
if idx != ll-1 {
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
}
//line app/vmalert/notifier/alertmanager_request.qtpl:23
//line app/vmalert/notifier/alertmanager_request.qtpl:22
}
//line app/vmalert/notifier/alertmanager_request.qtpl:23
//line app/vmalert/notifier/alertmanager_request.qtpl:22
qw422016.N().S(`},"annotations": {`)
//line app/vmalert/notifier/alertmanager_request.qtpl:26
//line app/vmalert/notifier/alertmanager_request.qtpl:25
c := len(alert.Annotations)
//line app/vmalert/notifier/alertmanager_request.qtpl:27
//line app/vmalert/notifier/alertmanager_request.qtpl:26
for k, v := range alert.Annotations {
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:27
c = c - 1
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().Q(k)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().S(`:`)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().Q(v)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
if c > 0 {
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
}
//line app/vmalert/notifier/alertmanager_request.qtpl:30
//line app/vmalert/notifier/alertmanager_request.qtpl:29
}
//line app/vmalert/notifier/alertmanager_request.qtpl:30
//line app/vmalert/notifier/alertmanager_request.qtpl:29
qw422016.N().S(`}}`)
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:32
if i != len(alerts)-1 {
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:32
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:32
}
//line app/vmalert/notifier/alertmanager_request.qtpl:34
//line app/vmalert/notifier/alertmanager_request.qtpl:33
}
//line app/vmalert/notifier/alertmanager_request.qtpl:34
//line app/vmalert/notifier/alertmanager_request.qtpl:33
qw422016.N().S(`]`)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
}
//line app/vmalert/notifier/alertmanager_request.qtpl:36
func writeamRequest(qq422016 qtio422016.Writer, alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) {
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
func writeamRequest(qq422016 qtio422016.Writer, alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) {
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
streamamRequest(qw422016, alerts, generatorURL, relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
streamamRequest(qw422016, alerts, generatorURL, lblss)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
}
//line app/vmalert/notifier/alertmanager_request.qtpl:36
func amRequest(alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) string {
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
func amRequest(alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) string {
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/notifier/alertmanager_request.qtpl:36
writeamRequest(qb422016, alerts, generatorURL, relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
writeamRequest(qb422016, alerts, generatorURL, lblss)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qs422016 := string(qb422016.B)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
return qs422016
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
}

View File

@@ -105,6 +105,16 @@ func TestAlertManager_Send(t *testing.T) {
if r.Header.Get(headerKey) != "bar" {
t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, "bar", r.Header.Get(headerKey))
}
case 4:
var a []struct {
Labels map[string]string `json:"labels"`
}
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
t.Fatalf("can not unmarshal data into alert %s", err)
}
if len(a) != 1 {
t.Fatalf("expected 1 alert in array got %d", len(a))
}
}
})
srv := httptest.NewServer(mux)
@@ -168,7 +178,20 @@ func TestAlertManager_Send(t *testing.T) {
t.Fatalf("unexpected error %s", err)
}
if c != 3 {
t.Fatalf("expected 3 calls(count from zero) to server got %d", c)
if err := am.Send(context.Background(), []Alert{
{
Name: "alert1",
Labels: map[string]string{"rule": "test"},
},
{
Name: "alert2",
Labels: map[string]string{},
},
}, map[string]string{}); err != nil {
t.Fatalf("unexpected error %s", err)
}
if c != 4 {
t.Fatalf("expected 4 calls(count from zero) to server got %d", c)
}
}

View File

@@ -614,7 +614,7 @@ func (ar *AlertingRule) alertToTimeSeries(a *notifier.Alert, timestamp int64) []
}
func alertToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries {
var labels []prompbmarshal.Label
labels := make([]prompbmarshal.Label, 0, len(a.Labels)+2)
for k, v := range a.Labels {
labels = append(labels, prompbmarshal.Label{
Name: k,
@@ -634,7 +634,7 @@ func alertToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSer
// alertForToTimeSeries returns a time series that represents
// state of active alerts, where value is time when alert become active
func alertForToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries {
var labels []prompbmarshal.Label
labels := make([]prompbmarshal.Label, 0, len(a.Labels)+1)
for k, v := range a.Labels {
labels = append(labels, prompbmarshal.Label{
Name: k,
@@ -650,21 +650,24 @@ func alertForToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.Time
// for alerts which changed their state from Pending to Inactive or Firing.
func pendingAlertStaleTimeSeries(ls map[string]string, timestamp int64, includeAlertForState bool) []prompbmarshal.TimeSeries {
var result []prompbmarshal.TimeSeries
var baseLabels []prompbmarshal.Label
baseLabels := make([]prompbmarshal.Label, 0, len(ls)+1)
for k, v := range ls {
baseLabels = append(baseLabels, prompbmarshal.Label{
Name: k,
Value: v,
})
}
alertsLabels := make([]prompbmarshal.Label, 0, len(ls)+2)
alertsLabels = append(alertsLabels, baseLabels...)
// __name__ already been dropped, no need to check duplication
alertsLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: alertStateLabel, Value: notifier.StatePending.String()})
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsLabels))
if includeAlertForState {
alertsForStateLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsForStateLabels))
baseLabels = append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, baseLabels))
}
return result
}
@@ -672,22 +675,25 @@ func pendingAlertStaleTimeSeries(ls map[string]string, timestamp int64, includeA
// firingAlertStaleTimeSeries returns stale `ALERTS` and `ALERTS_FOR_STATE` time series
// for alerts which changed their state from Firing to Inactive.
func firingAlertStaleTimeSeries(ls map[string]string, timestamp int64) []prompbmarshal.TimeSeries {
var baseLabels []prompbmarshal.Label
baseLabels := make([]prompbmarshal.Label, 0, len(ls)+1)
for k, v := range ls {
baseLabels = append(baseLabels, prompbmarshal.Label{
Name: k,
Value: v,
})
}
alertsLabels := make([]prompbmarshal.Label, 0, len(ls)+2)
alertsLabels = append(alertsLabels, baseLabels...)
// __name__ already been dropped, no need to check duplication
alertsLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: alertStateLabel, Value: notifier.StateFiring.String()})
alertsForStateLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
baseLabels = append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
return []prompbmarshal.TimeSeries{
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsLabels),
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsForStateLabels),
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, baseLabels),
}
}

View File

@@ -252,10 +252,14 @@ func TestAlertingRule_Exec(t *testing.T) {
},
map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "empty_labels"}, {Name: "alertstate", Value: "firing"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "empty_labels"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "empty_labels"}, {Name: "alertstate", Value: "firing"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "empty_labels"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
})
@@ -273,22 +277,34 @@ func TestAlertingRule_Exec(t *testing.T) {
4: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StateInactive}}},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
2: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
},
})
@@ -344,34 +360,54 @@ func TestAlertingRule_Exec(t *testing.T) {
},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
// stale time series for foo, `firing -> inactive`
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
// new time series for foo1
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
2: {
// stale time series for foo1
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
// new time series for foo2
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
},
})
@@ -389,50 +425,72 @@ func TestAlertingRule_Exec(t *testing.T) {
1: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StateFiring}}},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
// stale time series for `pending -> firing`
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
})
f(newTestAlertingRule("for-pending=>empty", time.Second), [][]datasource.Metric{
{metricWithLabels(t, "name", "foo")},
{metricWithLabels(t, "name", "foo")},
{metricWithLabels(t, "name", "foo", "a1", "b1", "a2", "b2", "a3", "b3")},
{metricWithLabels(t, "name", "foo", "a1", "b1", "a2", "b2", "a3", "b3")},
// empty step to delete pending alerts
{},
}, map[int][]testAlert{
0: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StatePending}}},
1: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StatePending}}},
0: {{labels: []string{"name", "foo", "a1", "b1", "a2", "b2", "a3", "b3"}, alert: &notifier.Alert{State: notifier.StatePending}}},
1: {{labels: []string{"name", "foo", "a1", "b1", "a2", "b2", "a3", "b3"}, alert: &notifier.Alert{State: notifier.StatePending}}},
2: {},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
// stale time series for `pending -> inactive`
2: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
},
})

View File

@@ -443,8 +443,8 @@ func (g *Group) Start(ctx context.Context, nts func() []notifier.Notifier, rw re
}
// UpdateWith inserts new group to updateCh
func (g *Group) UpdateWith(new *Group) {
g.updateCh <- new
func (g *Group) UpdateWith(newGroup *Group) {
g.updateCh <- newGroup
}
// DeepCopy returns a deep copy of group

View File

@@ -7,11 +7,13 @@ import (
"flag"
"fmt"
"math"
"net"
"net/http"
"net/url"
"os"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
@@ -348,6 +350,7 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
hostToAddrs := make(map[string][]string)
for _, bu := range up.busOriginal {
host := bu.Hostname()
port := bu.Port()
if hostToAddrs[host] != nil {
// ips for the given host have been already discovered
continue
@@ -364,7 +367,11 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
} else {
resolvedAddrs = make([]string, len(addrs))
for i, addr := range addrs {
resolvedAddrs[i] = fmt.Sprintf("%s:%d", addr.Target, addr.Port)
hostPort := port
if hostPort == "" && addr.Port > 0 {
hostPort = strconv.FormatUint(uint64(addr.Port), 10)
}
resolvedAddrs[i] = net.JoinHostPort(addr.Target, hostPort)
}
}
} else {
@@ -375,7 +382,7 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
} else {
resolvedAddrs = make([]string, len(addrs))
for i, addr := range addrs {
resolvedAddrs[i] = addr.String()
resolvedAddrs[i] = net.JoinHostPort(addr.String(), port)
}
}
}
@@ -389,17 +396,9 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
var busNew []*backendURL
for _, bu := range up.busOriginal {
host := bu.Hostname()
port := bu.Port()
for _, addr := range hostToAddrs[host] {
buCopy := *bu
buCopy.Host = addr
if port != "" {
if n := strings.IndexByte(buCopy.Host, ':'); n >= 0 {
// Drop the discovered port and substitute it the port specified in bu.
buCopy.Host = buCopy.Host[:n]
}
buCopy.Host += ":" + port
}
busNew = append(busNew, &backendURL{
url: &buCopy,
})

View File

@@ -3,12 +3,14 @@ package main
import (
"bytes"
"fmt"
"net"
"net/url"
"testing"
"gopkg.in/yaml.v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
)
func TestParseAuthConfigFailure(t *testing.T) {
@@ -799,6 +801,75 @@ func TestBrokenBackend(t *testing.T) {
}
}
func TestDiscoverBackendIPsWithIPV6(t *testing.T) {
f := func(actualUrl, expectedUrl string) {
t.Helper()
up := mustParseURL(actualUrl)
up.discoverBackendIPs = true
up.loadBalancingPolicy = "least_loaded"
up.discoverBackendAddrsIfNeeded()
pbus := up.bus.Load()
bus := *pbus
if len(bus) != 1 {
t.Fatalf("expected url list to be of size 1; got %d instead", len(bus))
}
got := bus[0].url.Host
if got != expectedUrl {
t.Fatalf(`expected url to be %q; got %q instead`, expectedUrl, bus[0].url.Host)
}
}
// Discover backendURL with SRV hostnames
customResolver := &fakeResolver{
Resolver: &net.Resolver{},
// SRV records must return hostname
// not an IP address
lookupSRVResults: map[string][]*net.SRV{
"_vmselect._tcp.selectwithport.": {
{
Target: "vmselect.local",
Port: 8481,
},
},
"_vmselect._tcp.selectwoport.": {
{
Target: "vmselect.local",
},
},
},
lookupIPAddrResults: map[string][]net.IPAddr{
"vminsert.local": {
{
IP: net.ParseIP("10.0.10.13"),
},
},
"ipv6.vminsert.local": {
{
IP: net.ParseIP("2607:f8b0:400a:80b::200e"),
},
},
},
}
origResolver := netutil.Resolver
netutil.Resolver = customResolver
defer func() {
netutil.Resolver = origResolver
}()
f("http://srv+_vmselect._tcp.selectwithport.:8080", "vmselect.local:8080")
f("http://srv+_vmselect._tcp.selectwithport.:", "vmselect.local:8481")
f("http://srv+_vmselect._tcp.selectwoport.:8080", "vmselect.local:8080")
f("http://srv+_vmselect._tcp.selectwoport.", "vmselect.local:")
f("http://vminsert.local:8080", "10.0.10.13:8080")
f("http://vminsert.local", "10.0.10.13:")
f("http://ipv6.vminsert.local:8080", "[2607:f8b0:400a:80b::200e]:8080")
f("http://ipv6.vminsert.local", "[2607:f8b0:400a:80b::200e]:")
}
func getRegexs(paths []string) []*Regex {
var sps []*Regex
for _, path := range paths {

View File

@@ -195,7 +195,7 @@ unauthorized_user:
}
responseExpected = `
statusCode=401
The provided authKey doesn't match -reloadAuthKey`
Expected to receive non-empty authKey when -reloadAuthKey is set`
f(cfgStr, requestURL, backendHandler, responseExpected)
if err := reloadAuthKey.Set(origAuthKey); err != nil {
t.Fatalf("unexpected error: %s", err)

View File

@@ -180,11 +180,7 @@ func (c *Client) Explore() ([]*Series, error) {
log.Printf("skip measurement %q since it has no fields", s.Measurement)
continue
}
tags, ok := measurementTags[s.Measurement]
if !ok {
return nil, fmt.Errorf("failed to find tags of measurement %s", s.Measurement)
}
emptyTags := getEmptyTags(tags, s.LabelPairs)
emptyTags := getEmptyTags(measurementTags[s.Measurement], s.LabelPairs)
for _, field := range fields {
is := &Series{
Measurement: s.Measurement,
@@ -201,11 +197,16 @@ func (c *Client) Explore() ([]*Series, error) {
// getEmptyTags returns tags of a measurement that are missing in a specific series.
// Tags represent all tags of a measurement. LabelPairs represent tags of a specific series.
func getEmptyTags(tags map[string]struct{}, LabelPairs []LabelPair) []string {
if len(tags) == 0 {
// fast path: the measurement does not contain any tag
return nil
}
labelMap := make(map[string]struct{})
for _, pair := range LabelPairs {
labelMap[pair.Name] = struct{}{}
}
result := make([]string, 0, len(labelMap)-len(LabelPairs))
var result []string
for tag := range tags {
if _, ok := labelMap[tag]; !ok {
result = append(result, tag)

View File

@@ -40,15 +40,15 @@ type filter struct {
labelValue string
}
func (f filter) inRange(min, max int64) bool {
func (f filter) inRange(minV, maxV int64) bool {
fmin, fmax := f.min, f.max
if min == 0 {
fmin = min
if minV == 0 {
fmin = minV
}
if fmax == 0 {
fmax = max
fmax = maxV
}
return min <= fmax && fmin <= max
return minV <= fmax && fmin <= maxV
}
// NewClient creates and validates new Client
@@ -59,13 +59,13 @@ func NewClient(cfg Config) (*Client, error) {
return nil, fmt.Errorf("failed to open snapshot %q: %s", cfg.Snapshot, err)
}
c := &Client{DBReadOnly: db}
min, max, err := parseTime(cfg.Filter.TimeMin, cfg.Filter.TimeMax)
minTime, maxTime, err := parseTime(cfg.Filter.TimeMin, cfg.Filter.TimeMax)
if err != nil {
return nil, fmt.Errorf("failed to parse time in filter: %s", err)
}
c.filter = filter{
min: min,
max: max,
min: minTime,
max: maxTime,
label: cfg.Filter.Label,
labelValue: cfg.Filter.LabelValue,
}

View File

@@ -105,7 +105,7 @@ func (ctx *InsertCtx) TryPrepareLabels(hasRelabeling bool) bool {
if timeserieslimits.Enabled() && timeserieslimits.IsExceeding(ctx.Labels) {
return false
}
ctx.sortLabelsIfNeeded()
ctx.SortLabelsIfNeeded()
return true
}

View File

@@ -12,8 +12,8 @@ var sortLabels = flag.Bool("sortLabels", false, `Whether to sort labels for inco
`For example, if m{k1="v1",k2="v2"} may be sent as m{k2="v2",k1="v1"}. `+
`Enabled sorting for labels can slow down ingestion performance a bit`)
// sortLabelsIfNeeded sorts labels if -sortLabels command-line flag is set
func (ctx *InsertCtx) sortLabelsIfNeeded() {
// SortLabelsIfNeeded sorts labels if -sortLabels command-line flag is set
func (ctx *InsertCtx) SortLabelsIfNeeded() {
if *sortLabels {
sort.Sort(&ctx.Labels)
}

View File

@@ -118,8 +118,14 @@ func insertRows(db string, rows []parser.Row, extraLabels []prompbmarshal.Label)
}
}
} else {
if !ic.TryPrepareLabels(false) {
continue
// special case for optimisations below
// do not call TryPrepareLabels
// manually apply sort and limits on demand
ic.SortLabelsIfNeeded()
if hasLimitsEnabled {
if timeserieslimits.IsExceeding(ic.Labels) {
continue
}
}
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf[:0], ic.Labels)
labelsLen := len(ic.Labels)
@@ -132,8 +138,6 @@ func insertRows(db string, rows []parser.Row, extraLabels []prompbmarshal.Label)
ic.Labels = ic.Labels[:labelsLen]
ic.AddLabel("", metricGroup)
if hasLimitsEnabled {
// special case for optimisation above
// check only __name__ label value limits
if timeserieslimits.IsExceeding(ic.Labels[len(ic.Labels)-1:]) {
continue
}

View File

@@ -98,13 +98,13 @@ func aggrMin(values []float64) float64 {
if pos < 0 {
return nan
}
min := values[pos]
minV := values[pos]
for _, v := range values[pos+1:] {
if !math.IsNaN(v) && v < min {
min = v
if !math.IsNaN(v) && v < minV {
minV = v
}
}
return min
return minV
}
func aggrMax(values []float64) float64 {
@@ -112,13 +112,13 @@ func aggrMax(values []float64) float64 {
if pos < 0 {
return nan
}
max := values[pos]
maxV := values[pos]
for _, v := range values[pos+1:] {
if !math.IsNaN(v) && v > max {
max = v
if !math.IsNaN(v) && v > maxV {
maxV = v
}
}
return max
return maxV
}
func aggrDiff(values []float64) float64 {
@@ -177,12 +177,12 @@ func aggrCount(values []float64) float64 {
}
func aggrRange(values []float64) float64 {
min := aggrMin(values)
if math.IsNaN(min) {
minV := aggrMin(values)
if math.IsNaN(minV) {
return nan
}
max := aggrMax(values)
return max - min
maxV := aggrMax(values)
return maxV - minV
}
func aggrMultiply(values []float64) float64 {

View File

@@ -2594,17 +2594,17 @@ func transformMinMax(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc, e
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
min := aggrMin(values)
if math.IsNaN(min) {
min = 0
minV := aggrMin(values)
if math.IsNaN(minV) {
minV = 0
}
max := aggrMax(values)
if math.IsNaN(max) {
max = 0
maxV := aggrMax(values)
if math.IsNaN(maxV) {
maxV = 0
}
vRange := max - min
vRange := maxV - minV
for i, v := range values {
v = (v - min) / vRange
v = (v - minV) / vRange
if math.IsInf(v, 0) {
v = 0
}
@@ -2975,9 +2975,9 @@ func transformRemoveAbovePercentile(ec *evalConfig, fe *graphiteql.FuncExpr) (ne
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
max := aggrFunc(values)
maxV := aggrFunc(values)
for i, v := range values {
if v > max {
if v > maxV {
values[i] = nan
}
}
@@ -3035,9 +3035,9 @@ func transformRemoveBelowPercentile(ec *evalConfig, fe *graphiteql.FuncExpr) (ne
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
min := aggrFunc(values)
minV := aggrFunc(values)
for i, v := range values {
if v < min {
if v < minV {
values[i] = nan
}
}
@@ -4514,11 +4514,11 @@ func transformOffsetToZero(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesF
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
min := aggrMin(values)
minV := aggrMin(values)
for i, v := range values {
values[i] = v - min
values[i] = v - minV
}
s.Tags["offsetToZero"] = fmt.Sprintf("%g", min)
s.Tags["offsetToZero"] = fmt.Sprintf("%g", minV)
s.Name = fmt.Sprintf("offsetToZero(%s)", s.Name)
s.expr = fe
s.pathExpression = s.Name
@@ -4567,29 +4567,29 @@ func transformPerSecond(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc
return f, nil
}
func nonNegativeDelta(curr, prev, max, min float64) (float64, float64) {
if !math.IsNaN(max) && curr > max {
func nonNegativeDelta(currV, prevV, maxV, minV float64) (float64, float64) {
if !math.IsNaN(maxV) && currV > maxV {
return nan, nan
}
if !math.IsNaN(min) && curr < min {
if !math.IsNaN(minV) && currV < minV {
return nan, nan
}
if math.IsNaN(curr) || math.IsNaN(prev) {
return nan, curr
if math.IsNaN(currV) || math.IsNaN(prevV) {
return nan, currV
}
if curr >= prev {
return curr - prev, curr
if currV >= prevV {
return currV - prevV, currV
}
if !math.IsNaN(max) {
if math.IsNaN(min) {
min = float64(0)
if !math.IsNaN(maxV) {
if math.IsNaN(minV) {
minV = float64(0)
}
return max + 1 + curr - prev - min, curr
return maxV + 1 + currV - prevV - minV, currV
}
if !math.IsNaN(min) {
return curr - min, curr
if !math.IsNaN(minV) {
return currV - minV, currV
}
return nan, curr
return nan, currV
}
// See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.threshold
@@ -4941,8 +4941,8 @@ func transformSortByMinima(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesF
}
// Filter out series with all the values smaller than 0
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
max := aggrMax(s.Values)
if math.IsNaN(max) || max <= 0 {
maxV := aggrMax(s.Values)
if math.IsNaN(maxV) || maxV <= 0 {
return nil, nil
}
return s, nil

View File

@@ -29,13 +29,13 @@ import (
)
var (
deleteAuthKey = flagutil.NewPassword("deleteAuthKey", "authKey for metrics' deletion via /api/v1/admin/tsdb/delete_series and /tags/delSeries. It overrides -httpAuth.*")
deleteAuthKey = flagutil.NewPassword("deleteAuthKey", "authKey for metrics' deletion via /api/v1/admin/tsdb/delete_series and /tags/delSeries. It could be passed via authKey query arg. It overrides -httpAuth.*")
maxConcurrentRequests = flag.Int("search.maxConcurrentRequests", getDefaultMaxConcurrentRequests(), "The maximum number of concurrent search requests. "+
"It shouldn't be high, since a single request can saturate all the CPU cores, while many concurrently executed requests may require high amounts of memory. "+
"See also -search.maxQueueDuration and -search.maxMemoryPerQuery")
maxQueueDuration = flag.Duration("search.maxQueueDuration", 10*time.Second, "The maximum time the request waits for execution when -search.maxConcurrentRequests "+
"limit is reached; see also -search.maxQueryDuration")
resetCacheAuthKey = flagutil.NewPassword("search.resetCacheAuthKey", "Optional authKey for resetting rollup cache via /internal/resetRollupResultCache call. It overrides -httpAuth.*")
resetCacheAuthKey = flagutil.NewPassword("search.resetCacheAuthKey", "Optional authKey for resetting rollup cache via /internal/resetRollupResultCache call. It could be passed via authKey query arg. It overrides -httpAuth.*")
logSlowQueryDuration = flag.Duration("search.logSlowQueryDuration", 5*time.Second, "Log queries with execution time exceeding this value. Zero disables slow query logging. "+
"See also -search.logQueryMemoryUsage")
vmalertProxyURL = flag.String("vmalert.proxyURL", "", "Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules")

View File

@@ -481,6 +481,8 @@ func DeleteHandler(startTime time.Time, r *http.Request) error {
if err != nil {
return err
}
cp.deadline = searchutils.GetDeadlineForDelete(r, startTime)
if !cp.IsDefaultTimeRange() {
return fmt.Errorf("start=%d and end=%d args aren't supported. Remove these args from the query in order to delete all the matching metrics", cp.start, cp.end)
}

View File

@@ -295,13 +295,13 @@ func aggrFuncMin(tss []*timeseries) []*timeseries {
}
dst := tss[0]
for i := range dst.Values {
min := dst.Values[i]
minV := dst.Values[i]
for _, ts := range tss {
if math.IsNaN(min) || ts.Values[i] < min {
min = ts.Values[i]
if math.IsNaN(minV) || ts.Values[i] < minV {
minV = ts.Values[i]
}
}
dst.Values[i] = min
dst.Values[i] = minV
}
return tss[:1]
}
@@ -313,13 +313,13 @@ func aggrFuncMax(tss []*timeseries) []*timeseries {
}
dst := tss[0]
for i := range dst.Values {
max := dst.Values[i]
maxV := dst.Values[i]
for _, ts := range tss {
if math.IsNaN(max) || ts.Values[i] > max {
max = ts.Values[i]
if math.IsNaN(maxV) || ts.Values[i] > maxV {
maxV = ts.Values[i]
}
}
dst.Values[i] = max
dst.Values[i] = maxV
}
return tss[:1]
}
@@ -793,7 +793,7 @@ func fillNaNsAtIdx(idx int, k float64, tss []*timeseries) {
}
}
func getIntK(k float64, max int) int {
func getIntK(k float64, maxV int) int {
if math.IsNaN(k) {
return 0
}
@@ -801,38 +801,38 @@ func getIntK(k float64, max int) int {
if kn < 0 {
return 0
}
if kn > max {
return max
if kn > maxV {
return maxV
}
return kn
}
func minValue(values []float64) float64 {
min := nan
for len(values) > 0 && math.IsNaN(min) {
min = values[0]
minV := nan
for len(values) > 0 && math.IsNaN(minV) {
minV = values[0]
values = values[1:]
}
for _, v := range values {
if !math.IsNaN(v) && v < min {
min = v
if !math.IsNaN(v) && v < minV {
minV = v
}
}
return min
return minV
}
func maxValue(values []float64) float64 {
max := nan
for len(values) > 0 && math.IsNaN(max) {
max = values[0]
maxV := nan
for len(values) > 0 && math.IsNaN(maxV) {
maxV = values[0]
values = values[1:]
}
for _, v := range values {
if !math.IsNaN(v) && v > max {
max = v
if !math.IsNaN(v) && v > maxV {
maxV = v
}
}
return max
return maxV
}
func avgValue(values []float64) float64 {

View File

@@ -46,6 +46,8 @@ var (
"so there is no need in spending additional CPU time on its handling. Staleness markers may exist only in data obtained from Prometheus scrape targets")
minWindowForInstantRollupOptimization = flag.Duration("search.minWindowForInstantRollupOptimization", time.Hour*3, "Enable cache-based optimization for repeated queries "+
"to /api/v1/query (aka instant queries), which contain rollup functions with lookbehind window exceeding the given value")
maxBinaryOpPushdownLabelValues = flag.Int("search.maxBinaryOpPushdownLabelValues", 100, "The maximum number of values for a label in the first expression that can be extracted as a common label filter and pushed down to the second expression in a binary operation. "+
"A larger value makes the pushed-down filter more complex but fewer time series will be returned. This flag is useful when selective label contains numerous values, for example `instance`, and storage resources are abundant.")
)
// The minimum number of points per timeseries for enabling time rounding.
@@ -582,7 +584,7 @@ func getCommonLabelFilters(tss []*timeseries) []metricsql.LabelFilter {
}
continue
}
if len(vc.values) > 100 {
if len(vc.values) > *maxBinaryOpPushdownLabelValues {
// Too many unique values found for the given tag.
// Do not make a filter on such values, since it may slow down
// search for matching time series.

View File

@@ -6,8 +6,6 @@ import (
"math"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
@@ -16,7 +14,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/metricsql"
)
@@ -270,7 +267,7 @@ func getReverseCmpOp(op string) string {
}
func parsePromQLWithCache(q string) (metricsql.Expr, error) {
pcv := parseCacheV.Get(q)
pcv := parseCacheV.get(q)
if pcv == nil {
e, err := metricsql.Parse(q)
if err == nil {
@@ -284,7 +281,7 @@ func parsePromQLWithCache(q string) (metricsql.Expr, error) {
e: e,
err: err,
}
parseCacheV.Put(q, pcv)
parseCacheV.put(q, pcv)
}
if pcv.err != nil {
return nil, pcv.err
@@ -328,80 +325,3 @@ func escapeDots(s string) string {
}
return string(result)
}
var parseCacheV = func() *parseCache {
pc := &parseCache{
m: make(map[string]*parseCacheValue),
}
metrics.NewGauge(`vm_cache_requests_total{type="promql/parse"}`, func() float64 {
return float64(pc.Requests())
})
metrics.NewGauge(`vm_cache_misses_total{type="promql/parse"}`, func() float64 {
return float64(pc.Misses())
})
metrics.NewGauge(`vm_cache_entries{type="promql/parse"}`, func() float64 {
return float64(pc.Len())
})
return pc
}()
const parseCacheMaxLen = 10e3
type parseCacheValue struct {
e metricsql.Expr
err error
}
type parseCache struct {
requests atomic.Uint64
misses atomic.Uint64
m map[string]*parseCacheValue
mu sync.RWMutex
}
func (pc *parseCache) Requests() uint64 {
return pc.requests.Load()
}
func (pc *parseCache) Misses() uint64 {
return pc.misses.Load()
}
func (pc *parseCache) Len() uint64 {
pc.mu.RLock()
n := len(pc.m)
pc.mu.RUnlock()
return uint64(n)
}
func (pc *parseCache) Get(q string) *parseCacheValue {
pc.requests.Add(1)
pc.mu.RLock()
pcv := pc.m[q]
pc.mu.RUnlock()
if pcv == nil {
pc.misses.Add(1)
}
return pcv
}
func (pc *parseCache) Put(q string, pcv *parseCacheValue) {
pc.mu.Lock()
overflow := len(pc.m) - parseCacheMaxLen
if overflow > 0 {
// Remove 10% of items from the cache.
overflow = int(float64(len(pc.m)) * 0.1)
for k := range pc.m {
delete(pc.m, k)
overflow--
if overflow <= 0 {
break
}
}
}
pc.m[q] = pcv
pc.mu.Unlock()
}

View File

@@ -0,0 +1,142 @@
// Cache for metricsql expressions
// Based on the fastcache idea of locking buckets in order to avoid whole cache locks.
// See: https://github.com/VictoriaMetrics/fastcache
package promql
import (
"sync"
"sync/atomic"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/metricsql"
xxhash "github.com/cespare/xxhash/v2"
)
var parseCacheV = func() *parseCache {
pc := newParseCache()
metrics.NewGauge(`vm_cache_requests_total{type="promql/parse"}`, func() float64 {
return float64(pc.requests())
})
metrics.NewGauge(`vm_cache_misses_total{type="promql/parse"}`, func() float64 {
return float64(pc.misses())
})
metrics.NewGauge(`vm_cache_entries{type="promql/parse"}`, func() float64 {
return float64(pc.len())
})
return pc
}()
const (
parseBucketCount = 128
parseCacheMaxLen int = 10e3
parseBucketMaxLen int = parseCacheMaxLen / parseBucketCount
parseBucketFreePercent float64 = 0.1
)
type parseCacheValue struct {
e metricsql.Expr
err error
}
type parseBucket struct {
m map[string]*parseCacheValue
mu sync.RWMutex
requests atomic.Uint64
misses atomic.Uint64
}
type parseCache struct {
buckets [parseBucketCount]parseBucket
}
func newParseCache() *parseCache {
pc := new(parseCache)
for i := 0; i < parseBucketCount; i++ {
pc.buckets[i] = newParseBucket()
}
return pc
}
func (pc *parseCache) put(q string, pcv *parseCacheValue) {
h := xxhash.Sum64String(q)
idx := h % parseBucketCount
pc.buckets[idx].put(q, pcv)
}
func (pc *parseCache) get(q string) *parseCacheValue {
h := xxhash.Sum64String(q)
idx := h % parseBucketCount
return pc.buckets[idx].get(q)
}
func (pc *parseCache) requests() uint64 {
var n uint64
for i := 0; i < parseBucketCount; i++ {
n += pc.buckets[i].requests.Load()
}
return n
}
func (pc *parseCache) misses() uint64 {
var n uint64
for i := 0; i < parseBucketCount; i++ {
n += pc.buckets[i].misses.Load()
}
return n
}
func (pc *parseCache) len() uint64 {
var n uint64
for i := 0; i < parseBucketCount; i++ {
n += pc.buckets[i].len()
}
return n
}
func newParseBucket() parseBucket {
return parseBucket{
m: make(map[string]*parseCacheValue, parseBucketMaxLen),
}
}
func (pb *parseBucket) len() uint64 {
pb.mu.RLock()
n := len(pb.m)
pb.mu.RUnlock()
return uint64(n)
}
func (pb *parseBucket) get(q string) *parseCacheValue {
pb.requests.Add(1)
pb.mu.RLock()
pcv := pb.m[q]
pb.mu.RUnlock()
if pcv == nil {
pb.misses.Add(1)
}
return pcv
}
func (pb *parseBucket) put(q string, pcv *parseCacheValue) {
pb.mu.Lock()
overflow := len(pb.m) - parseBucketMaxLen
if overflow > 0 {
// Remove parseBucketDeletePercent*100 % of items from the bucket.
overflow = int(float64(len(pb.m)) * parseBucketFreePercent)
for k := range pb.m {
delete(pb.m, k)
overflow--
if overflow <= 0 {
break
}
}
}
pb.m[q] = pcv
pb.mu.Unlock()
}

View File

@@ -0,0 +1,129 @@
package promql
import (
"fmt"
"testing"
"github.com/VictoriaMetrics/metricsql"
)
func testGetParseCacheValue(q string) *parseCacheValue {
e, err := metricsql.Parse(q)
return &parseCacheValue{
e: e,
err: err,
}
}
func testGenerateQueries(items int) []string {
queries := make([]string, items)
for i := 0; i < items; i++ {
queries[i] = fmt.Sprintf(`node_time_seconds{instance="node%d", job="job%d"}`, i, i)
}
return queries
}
func TestParseCache(t *testing.T) {
pc := newParseCache()
if pc.len() != 0 || pc.misses() != 0 || pc.requests() != 0 {
t.Errorf("unexpected pc.Len()=%d, pc.Misses()=%d, pc.Requests()=%d; expected all to be zero.", pc.len(), pc.misses(), pc.requests())
}
q1 := `foo{bar="baz"}`
v1 := testGetParseCacheValue(q1)
q2 := `foo1{bar1="baz1"}`
v2 := testGetParseCacheValue(q2)
pc.put(q1, v1)
if pc.len() != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 1)
}
if res := pc.get(q2); res != nil {
t.Errorf("unexpected non-empty value obtained from cache: %d ", res)
}
if pc.len() != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 1)
}
if miss := pc.misses(); miss != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", miss, 1)
}
if req := pc.requests(); req != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", req, 1)
}
pc.put(q2, v2)
if pc.len() != 2 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 2)
}
if res := pc.get(q1); res != v1 {
t.Errorf("unexpected value obtained; got %v; want %v", res, v1)
}
if res := pc.get(q2); res != v2 {
t.Errorf("unexpected value obtained; got %v; want %v", res, v2)
}
pc.put(q2, v2)
if pc.len() != 2 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 2)
}
if miss := pc.misses(); miss != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", miss, 1)
}
if req := pc.requests(); req != 3 {
t.Errorf("unexpected value obtained; got %d; want %d", req, 3)
}
if res := pc.get(q2); res != v2 {
t.Errorf("unexpected value obtained; got %v; want %v", res, v2)
}
if pc.len() != 2 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 2)
}
if miss := pc.misses(); miss != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", miss, 1)
}
if req := pc.requests(); req != 4 {
t.Errorf("unexpected value obtained; got %d; want %d", req, 4)
}
}
func TestParseCacheBucketOverflow(t *testing.T) {
b := newParseBucket()
var expectedLen uint64
// +2 for overflow and clean up
queries := testGenerateQueries(parseBucketMaxLen + 2)
// Same value for all keys
v := testGetParseCacheValue(queries[0])
// Fill bucket
for i := 0; i < parseBucketMaxLen; i++ {
b.put(queries[i], v)
}
expectedLen = uint64(parseBucketMaxLen)
if b.len() != expectedLen {
t.Errorf("unexpected value obtained; got %v; want %v", b.len(), expectedLen)
}
// Overflow bucket
expectedLen = uint64(parseBucketMaxLen + 1)
b.put(queries[parseBucketMaxLen], v)
if b.len() != uint64(expectedLen) {
t.Errorf("unexpected value obtained; got %v; want %v", b.len(), expectedLen)
}
// Clean up;
oldLen := b.len()
overflow := int(float64(oldLen) * parseBucketFreePercent)
expectedLen = oldLen - uint64(overflow) + 1 // +1 for new entry
b.put(queries[parseBucketMaxLen+1], v)
if b.len() != expectedLen {
t.Errorf("unexpected value obtained; got %v; want %v", b.len(), expectedLen)
}
}

View File

@@ -0,0 +1,235 @@
package promql
import (
"testing"
)
func BenchmarkCachePutNoOverFlow(b *testing.B) {
const items int = (parseCacheMaxLen / 2)
pc := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < items; i++ {
pc.put(queries[i], v)
}
}
})
if pc.len() != uint64(items) {
b.Errorf("unexpected value obtained; got %d; want %d", pc.len(), items)
}
}
func BenchmarkCacheGetNoOverflow(b *testing.B) {
const items int = parseCacheMaxLen / 2
pc := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
for i := 0; i < len(queries); i++ {
pc.put(queries[i], v)
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < items; i++ {
if v := pc.get(queries[i]); v == nil {
b.Errorf("unexpected nil value obtained from cache for query: %s ", queries[i])
}
}
}
})
}
func BenchmarkCachePutGetNoOverflow(b *testing.B) {
const items int = parseCacheMaxLen / 2
pc := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < items; i++ {
pc.put(queries[i], v)
if res := pc.get(queries[i]); res == nil {
b.Errorf("unexpected nil value obtained from cache for query: %s ", queries[i])
}
}
}
})
if pc.len() != uint64(items) {
b.Errorf("unexpected value obtained; got %d; want %d", pc.len(), items)
}
}
func BenchmarkCachePutOverflow(b *testing.B) {
const items int = parseCacheMaxLen + (parseCacheMaxLen / 2)
c := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
for i := 0; i < parseCacheMaxLen; i++ {
c.put(queries[i], v)
}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := parseCacheMaxLen; i < items; i++ {
c.put(queries[i], v)
}
}
})
maxElemnts := uint64(parseCacheMaxLen + parseBucketCount)
if c.len() > maxElemnts {
b.Errorf("cache length is more than expected; got %d, expected %d", c.len(), maxElemnts)
}
}
func BenchmarkCachePutGetOverflow(b *testing.B) {
const items int = parseCacheMaxLen + (parseCacheMaxLen / 2)
c := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
for i := 0; i < parseCacheMaxLen; i++ {
c.put(queries[i], v)
}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := parseCacheMaxLen; i < items; i++ {
c.put(queries[i], v)
c.get(queries[i])
}
}
})
maxElemnts := uint64(parseCacheMaxLen + parseBucketCount)
if c.len() > maxElemnts {
b.Errorf("cache length is more than expected; got %d, expected %d", c.len(), maxElemnts)
}
}
var testSimpleQueries = []string{
`m{a="b"}`,
`{a="b"}`,
`m{c="d",a="b"}`,
`{a="b",c="d"}`,
`m1{a="foo"}`,
`m2{a="bar"}`,
`m1{b="foo"}`,
`m2{b="bar"}`,
`m1{a="foo",b="bar"}`,
`m2{b="bar",c="x"}`,
`{b="bar"}`,
}
func BenchmarkParsePromQLWithCacheSimple(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < len(testSimpleQueries); j++ {
_, err := parsePromQLWithCache(testSimpleQueries[j])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
}
func BenchmarkParsePromQLWithCacheSimpleParallel(b *testing.B) {
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < len(testSimpleQueries); i++ {
_, err := parsePromQLWithCache(testSimpleQueries[i])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
})
}
var testComplexQueries = []string{
`sort_desc(label_set(2, "foo", "bar") * ignoring(a) (label_set(time(), "foo", "bar") or label_set(10, "foo", "qwert")))`,
`sum(a.b{c="d.e",x=~"a.b.+[.a]",y!~"aaa.bb|cc.dd"}) + avg_over_time(1,sum({x=~"aa.bb"}))`,
`sort((label_set(time() offset 100s, "foo", "bar"), label_set(time()+10, "foo", "baz") offset 50s) offset 400s)`,
`sort(label_map((
label_set(time(), "label", "v1"),
label_set(time()+100, "label", "v2"),
label_set(time()+200, "label", "v3"),
label_set(time()+300, "x", "y"),
label_set(time()+400, "label", "v4"),
), "label", "v1", "foo", "v2", "bar", "", "qwe", "v4", ""))`,
`sort(labels_equal((
label_set(10, "instance", "qwe", "host", "rty"),
label_set(20, "instance", "qwe", "host", "qwe"),
label_set(30, "aaa", "bbb", "instance", "foo", "host", "foo"),
), "instance", "host"))`,
`with (
x = (
label_set(time() > 1500, "foo", "123.456", "__name__", "aaa"),
label_set(-time(), "foo", "bar", "__name__", "bbb"),
label_set(-time(), "__name__", "bxs"),
label_set(-time(), "foo", "45", "bar", "xs"),
)
)
sort(x + label_value(x, "foo"))`,
`label_replace(
label_replace(
label_replace(time(), "__name__", "x${1}y", "foo", ".*"),
"xxx", "foo${1}bar(${1})", "__name__", "(.+)"),
"xxx", "AA$1", "xxx", "foox(.+)"
)`,
`sort_desc(union(
label_set(time() > 1400, "__name__", "x", "foo", "bar"),
label_set(time() < 1700, "__name__", "y", "foo", "baz")) default 123)`,
`sort(histogram_quantile(0.6,
label_set(90, "foo", "bar", "le", "10")
or label_set(100, "foo", "bar", "le", "30")
or label_set(300, "foo", "bar", "le", "+Inf")
or label_set(200, "tag", "xx", "le", "10")
or label_set(300, "tag", "xx", "le", "30")
))`,
}
func BenchmarkParsePromQLWithCacheComplex(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < len(testComplexQueries); j++ {
_, err := parsePromQLWithCache(testComplexQueries[j])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
}
func BenchmarkParsePromQLWithCacheComplexParallel(b *testing.B) {
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < len(testComplexQueries); i++ {
_, err := parsePromQLWithCache(testComplexQueries[i])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
})
}

View File

@@ -520,7 +520,8 @@ type rollupFuncArg struct {
// Timestamps for values.
timestamps []int64
// Real value preceding values without restrictions on staleness interval.
// Real value preceding values.
// Is populated if preceding value is within the -search.maxStalenessInterval (rc.LookbackDelta).
realPrevValue float64
// Real value which goes after values.
@@ -764,10 +765,18 @@ func (rc *rollupConfig) doInternal(dstValues []float64, tsm *timeseriesMap, valu
}
rfa.values = values[i:j]
rfa.timestamps = timestamps[i:j]
rfa.realPrevValue = nan
if i > 0 {
rfa.realPrevValue = values[i-1]
} else {
rfa.realPrevValue = nan
prevValue, prevTimestamp := values[i-1], timestamps[i-1]
// set realPrevValue if rc.LookbackDelta == 0
// or if distance between datapoint in prev interval and beginning of this interval
// doesn't exceed LookbackDelta.
// https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1381
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/894
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8045
if rc.LookbackDelta == 0 || (tStart-prevTimestamp) < rc.LookbackDelta {
rfa.realPrevValue = prevValue
}
}
if j < len(values) {
rfa.realNextValue = values[j]
@@ -1682,9 +1691,9 @@ func rollupRateOverSum(rfa *rollupFuncArg) float64 {
}
func rollupRange(rfa *rollupFuncArg) float64 {
max := rollupMax(rfa)
min := rollupMin(rfa)
return max - min
maxV := rollupMax(rfa)
minV := rollupMin(rfa)
return maxV - minV
}
func rollupSum2(rfa *rollupFuncArg) float64 {
@@ -2192,38 +2201,38 @@ func rollupClose(rfa *rollupFuncArg) float64 {
func rollupHigh(rfa *rollupFuncArg) float64 {
values := getCandlestickValues(rfa)
max := getFirstValueForCandlestick(rfa)
if math.IsNaN(max) {
maxV := getFirstValueForCandlestick(rfa)
if math.IsNaN(maxV) {
if len(values) == 0 {
return nan
}
max = values[0]
maxV = values[0]
values = values[1:]
}
for _, v := range values {
if v > max {
max = v
if v > maxV {
maxV = v
}
}
return max
return maxV
}
func rollupLow(rfa *rollupFuncArg) float64 {
values := getCandlestickValues(rfa)
min := getFirstValueForCandlestick(rfa)
if math.IsNaN(min) {
minV := getFirstValueForCandlestick(rfa)
if math.IsNaN(minV) {
if len(values) == 0 {
return nan
}
min = values[0]
minV = values[0]
values = values[1:]
}
for _, v := range values {
if v < min {
min = v
if v < minV {
minV = v
}
}
return min
return minV
}
func rollupModeOverTime(rfa *rollupFuncArg) float64 {

View File

@@ -5,6 +5,8 @@ import (
"testing"
"github.com/VictoriaMetrics/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
)
var (
@@ -1587,3 +1589,229 @@ func TestRollupDelta(t *testing.T) {
f(1, nan, nan, nil, 0)
f(100, nan, nan, nil, 0)
}
func TestRollupDeltaWithStaleness(t *testing.T) {
// there is a gap between samples in the dataset below
timestamps := []int64{0, 15000, 30000, 70000}
values := []float64{1, 1, 1, 1}
// if step > gap, then delta will always respect value before gap
t.Run("step>gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 45000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// even if LookbackDelta < gap
t.Run("step>gap;LookbackDelta<gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 45000,
LookbackDelta: 10e3,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta==0 then delta will always respect value before gap
// as LookbackDelta=0 ignores staleness
t.Run("step<gap;LookbackDelta=0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 10000,
LookbackDelta: 0,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 0}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta>0 then delta will respect value before gap
// only if it is not stale according to LookbackDelta
t.Run("step<gap;LookbackDelta>0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
LookbackDelta: 30e3,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// there is a staleness marker between samples in the dataset below
timestamps = []int64{0, 10000, 20000, 30000, 40000}
values = []float64{1, 1, 1, decimal.StaleNaN, 1}
t.Run("staleness marker", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 40000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 10 {
t.Fatalf("expecting 10 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, nan, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
}
func TestRollupIncreasePureWithStaleness(t *testing.T) {
// there is a gap between samples in the dataset below
timestamps := []int64{0, 15000, 30000, 70000}
values := []float64{1, 1, 1, 1}
// if step > gap, then delta will always respect value before gap
t.Run("step>gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 45000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// even if LookbackDelta < gap
t.Run("step>gap;LookbackDelta<gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 45000,
LookbackDelta: 10e3,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta==0 then delta will always respect value before gap
// as LookbackDelta=0 ignores staleness
t.Run("step<gap;LookbackDelta=0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 10000,
LookbackDelta: 0,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 0}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta>0 then delta will respect value before gap
// only if it is not stale according to LookbackDelta
t.Run("step<gap;LookbackDelta>0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
LookbackDelta: 30e3,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// there is a staleness marker between samples in the dataset below
timestamps = []int64{0, 10000, 20000, 30000, 40000}
values = []float64{1, 1, 1, decimal.StaleNaN, 1}
t.Run("staleness marker", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 40000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 10 {
t.Fatalf("expecting 10 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, nan, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
}

View File

@@ -15,6 +15,7 @@ import (
var (
maxExportDuration = flag.Duration("search.maxExportDuration", time.Hour*24*30, "The maximum duration for /api/v1/export call")
maxDeleteDuration = flag.Duration("search.maxDeleteDuration", time.Minute*5, "The maximum duration for /api/v1/admin/tsdb/delete_series call")
maxQueryDuration = flag.Duration("search.maxQueryDuration", time.Second*30, "The maximum duration for query execution. It can be overridden to a smaller value on a per-query basis via 'timeout' query arg")
maxStatusRequestDuration = flag.Duration("search.maxStatusRequestDuration", time.Minute*5, "The maximum duration for /api/v1/status/* requests")
maxLabelsAPIDuration = flag.Duration("search.maxLabelsAPIDuration", time.Second*5, "The maximum duration for /api/v1/labels, /api/v1/label/.../values and /api/v1/series requests. "+
@@ -58,6 +59,12 @@ func GetDeadlineForLabelsAPI(r *http.Request, startTime time.Time) Deadline {
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxLabelsAPIDuration")
}
// GetDeadlineForDelete returns deadline for the given request to /api/v1/admin/tsdb/delete_series.
func GetDeadlineForDelete(r *http.Request, startTime time.Time) Deadline {
dMax := maxDeleteDuration.Milliseconds()
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxDeleteDuration")
}
func getDeadlineWithMaxDuration(r *http.Request, startTime time.Time, dMax int64, flagHint string) Deadline {
d, err := httputils.GetDuration(r, "timeout", 0)
if err != nil {

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.876c56b7.css",
"main.js": "./static/js/main.caf36c39.js",
"main.css": "./static/css/main.63479b72.css",
"main.js": "./static/js/main.256ee243.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.876c56b7.css",
"static/js/main.caf36c39.js"
"static/css/main.63479b72.css",
"static/js/main.256ee243.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:site" content="@https://victoriametrics.com/"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.caf36c39.js"></script><link href="./static/css/main.876c56b7.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:site" content="@https://victoriametrics.com/"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.256ee243.js"></script><link href="./static/css/main.63479b72.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -67,6 +67,8 @@ var (
"See https://docs.victoriametrics.com/single-server-victoriametrics/#cache-tuning")
cacheSizeIndexDBDataBlocks = flagutil.NewBytes("storage.cacheSizeIndexDBDataBlocks", 0, "Overrides max size for indexdb/dataBlocks cache. "+
"See https://docs.victoriametrics.com/single-server-victoriametrics/#cache-tuning")
cacheSizeIndexDBDataBlocksSparse = flagutil.NewBytes("storage.cacheSizeIndexDBDataBlocksSparse", 0, "Overrides max size for indexdb/dataBlocksSparse cache. "+
"See https://docs.victoriametrics.com/single-server-victoriametrics/#cache-tuning")
cacheSizeIndexDBTagFilters = flagutil.NewBytes("storage.cacheSizeIndexDBTagFilters", 0, "Overrides max size for indexdb/tagFiltersToMetricIDs cache. "+
"See https://docs.victoriametrics.com/single-server-victoriametrics/#cache-tuning")
)
@@ -100,6 +102,7 @@ func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
storage.SetTagFiltersCacheSize(cacheSizeIndexDBTagFilters.IntN())
mergeset.SetIndexBlocksCacheSize(cacheSizeIndexDBIndexBlocks.IntN())
mergeset.SetDataBlocksCacheSize(cacheSizeIndexDBDataBlocks.IntN())
mergeset.SetDataBlocksSparseCacheSize(cacheSizeIndexDBDataBlocksSparse.IntN())
if retentionPeriod.Duration() < 24*time.Hour {
logger.Fatalf("-retentionPeriod cannot be smaller than a day; got %s", retentionPeriod)
@@ -581,6 +584,7 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/next_day_metric_ids"}`, m.NextDayMetricIDCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/indexBlocks"}`, tm.IndexBlocksCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="indexdb/dataBlocks"}`, idbm.DataBlocksCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="indexdb/dataBlocksSparse"}`, idbm.DataBlocksSparseCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="indexdb/indexBlocks"}`, idbm.IndexBlocksCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="indexdb/tagFiltersToMetricIDs"}`, idbm.TagFiltersToMetricIDsCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/regexps"}`, uint64(storage.RegexpCacheSize()))
@@ -592,6 +596,7 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/metricName"}`, m.MetricNameCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/indexBlocks"}`, tm.IndexBlocksCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="indexdb/dataBlocks"}`, idbm.DataBlocksCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="indexdb/dataBlocksSparse"}`, idbm.DataBlocksSparseCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="indexdb/indexBlocks"}`, idbm.IndexBlocksCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/date_metricID"}`, m.DateMetricIDCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/hour_metric_ids"}`, m.HourMetricIDCacheSizeBytes)
@@ -606,6 +611,7 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/metricName"}`, m.MetricNameCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/indexBlocks"}`, tm.IndexBlocksCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="indexdb/dataBlocks"}`, idbm.DataBlocksCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="indexdb/dataBlocksSparse"}`, idbm.DataBlocksSparseCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="indexdb/indexBlocks"}`, idbm.IndexBlocksCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="indexdb/tagFiltersToMetricIDs"}`, idbm.TagFiltersToMetricIDsCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/regexps"}`, uint64(storage.RegexpCacheMaxSizeBytes()))
@@ -616,6 +622,7 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="storage/metricName"}`, m.MetricNameCacheRequests)
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="storage/indexBlocks"}`, tm.IndexBlocksCacheRequests)
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="indexdb/dataBlocks"}`, idbm.DataBlocksCacheRequests)
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="indexdb/dataBlocksSparse"}`, idbm.DataBlocksSparseCacheRequests)
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="indexdb/indexBlocks"}`, idbm.IndexBlocksCacheRequests)
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="indexdb/tagFiltersToMetricIDs"}`, idbm.TagFiltersToMetricIDsCacheRequests)
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="storage/regexps"}`, storage.RegexpCacheRequests())
@@ -626,6 +633,7 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="storage/metricName"}`, m.MetricNameCacheMisses)
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="storage/indexBlocks"}`, tm.IndexBlocksCacheMisses)
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="indexdb/dataBlocks"}`, idbm.DataBlocksCacheMisses)
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="indexdb/dataBlocksSparse"}`, idbm.DataBlocksSparseCacheMisses)
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="indexdb/indexBlocks"}`, idbm.IndexBlocksCacheMisses)
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="indexdb/tagFiltersToMetricIDs"}`, idbm.TagFiltersToMetricIDsCacheMisses)
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="storage/regexps"}`, storage.RegexpCacheMisses())

View File

@@ -6,7 +6,7 @@ COPY web/ /build/
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o web-amd64 github.com/VictoriMetrics/vmui/ && \
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o web-windows github.com/VictoriMetrics/vmui/
FROM alpine:3.21.0
FROM alpine:3.21.2
USER root
COPY --from=build-web-stage /build/web-amd64 /app/web

View File

@@ -56,3 +56,8 @@ export interface ReportMetaData {
comment: string;
params: Record<string, string>;
}
export interface LogsFiledValues {
value: string;
hits: number;
}

View File

@@ -5,7 +5,6 @@ import "./style.scss";
import useStateSearchParams from "../../../../hooks/useStateSearchParams";
import { useSearchParams } from "react-router-dom";
import Button from "../../../Main/Button/Button";
import classNames from "classnames";
import { SettingsIcon, VisibilityIcon, VisibilityOffIcon } from "../../../Main/Icons";
import Tooltip from "../../../Main/Tooltip/Tooltip";
import Popper from "../../../Main/Popper/Popper";
@@ -24,27 +23,20 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
setFalse: handleCloseOptions,
} = useBoolean(false);
const [graphStyle, setGraphStyle] = useStateSearchParams(GRAPH_STYLES.LINE_STEPPED, "graph");
const [stacked, setStacked] = useStateSearchParams(false, "stacked");
const [fill, setFill] = useStateSearchParams(false, "fill");
const [fill, setFill] = useStateSearchParams("true", "fill");
const [hideChart, setHideChart] = useStateSearchParams(false, "hide_chart");
const options: GraphOptions = useMemo(() => ({
graphStyle,
graphStyle: GRAPH_STYLES.BAR,
stacked,
fill,
fill: fill === "true",
hideChart,
}), [graphStyle, stacked, fill, hideChart]);
const handleChangeGraphStyle = (val: string) => () => {
setGraphStyle(val as GRAPH_STYLES);
searchParams.set("graph", val);
setSearchParams(searchParams);
};
}), [stacked, fill, hideChart]);
const handleChangeFill = (val: boolean) => {
setFill(val);
val ? searchParams.set("fill", "true") : searchParams.delete("fill");
setFill(`${val}`);
searchParams.set("fill", `${val}`);
setSearchParams(searchParams);
};
@@ -97,21 +89,6 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
title={"Graph settings"}
>
<div className="vm-bar-hits-options-settings">
<div className="vm-bar-hits-options-settings-item vm-bar-hits-options-settings-item_list">
<p className="vm-bar-hits-options-settings-item__title">Graph style:</p>
{Object.values(GRAPH_STYLES).map(style => (
<div
key={style}
className={classNames({
"vm-list-item": true,
"vm-list-item_active": graphStyle === style,
})}
onClick={handleChangeGraphStyle(style)}
>
{style}
</div>
))}
</div>
<div className="vm-bar-hits-options-settings-item">
<Switch
label={"Stacked"}
@@ -122,7 +99,7 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
<div className="vm-bar-hits-options-settings-item">
<Switch
label={"Fill"}
value={fill}
value={fill === "true"}
onChange={handleChangeFill}
/>
</div>

View File

@@ -11,12 +11,12 @@
&-settings {
display: grid;
align-items: flex-start;
gap: $padding-global;
min-width: 200px;
gap: $padding-global;
padding-bottom: $padding-global;
&-item {
border-bottom: $border-divider;
padding: 0 $padding-global $padding-global;
padding: 0 $padding-global;
&_list {
padding: 0;

View File

@@ -0,0 +1,154 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from "preact/compat";
import Autocomplete, { AutocompleteOptions } from "../../../Main/Autocomplete/Autocomplete";
import { AUTOCOMPLETE_LIMITS } from "../../../../constants/queryAutocomplete";
import { QueryEditorAutocompleteProps } from "../QueryEditor";
import { getContextData, splitLogicalParts } from "./parser";
import { ContextType, LogicalPart, LogicalPartType } from "./types";
import { useFetchLogsQLOptions } from "./useFetchLogsQLOptions";
import { pipeList } from "./pipes";
const LogsQueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
value,
anchorEl,
caretPosition,
hasHelperText,
onSelect,
onFoundOptions
}) => {
const [offsetPos, setOffsetPos] = useState({ top: 0, left: 0 });
const fullValue = useMemo(() => {
if (caretPosition[0] !== caretPosition[1]) return { valueBeforeCursor: value, valueAfterCursor: "" };
const valueBeforeCursor = value.substring(0, caretPosition[0]);
const valueAfterCursor = value.substring(caretPosition[1]);
return { valueBeforeCursor, valueAfterCursor };
}, [value, caretPosition]);
const logicalParts = useMemo(() => {
return splitLogicalParts(value);
}, [value]);
const contextData = useMemo(() => {
if (caretPosition[0] !== caretPosition[1]) return;
const part = logicalParts.find(p => caretPosition[0] >= p.position[0] && caretPosition[0] <= p.position[1]);
if (!part) return;
const cursorStartPosition = caretPosition[0] - part.position[0];
return {
...part,
...getContextData(part, cursorStartPosition)
};
}, [logicalParts, caretPosition]);
const { fieldNames, fieldValues, loading } = useFetchLogsQLOptions(contextData);
const options = useMemo(() => {
switch (contextData?.contextType) {
case ContextType.FilterName:
case ContextType.FilterUnknown:
return fieldNames;
case ContextType.FilterValue:
return fieldValues;
case ContextType.PipeName:
return pipeList;
default:
return [];
}
}, [contextData, fieldNames, fieldValues]);
const getUpdatedValue = (insertValue: string, logicalParts: LogicalPart[], id?: number) => {
return logicalParts.reduce((acc, part) => {
const value = part.id === id ? insertValue : part.value;
const separator = part.type === LogicalPartType.Pipe ? " | " : " ";
return `${acc}${separator}${value}`;
}, "").trim();
};
const getModifyInsert = (insert: string, contextType: ContextType, value = "", insertType?: string) => {
let modifiedInsert = insert;
if (insertType === ContextType.FilterName) {
modifiedInsert += ":";
} else if (contextType === ContextType.FilterValue) {
const insertWithQuotes = value.startsWith("_stream:") ? modifiedInsert : `"${modifiedInsert}"`;
modifiedInsert = `${contextData?.filterName || ""}:${insertWithQuotes}`;
}
return modifiedInsert;
};
const handleSelect = useCallback((insert: string, item: AutocompleteOptions) => {
const {
id,
contextType = ContextType.FilterUnknown,
value = "",
position = [0, 0]
} = contextData || {};
const insertValue = getModifyInsert(insert, contextType, value, item.type);
const newValue = getUpdatedValue(insertValue, logicalParts, id);
const updatedPosition = (position[0] || 1) + insertValue.length + (item.type === ContextType.PipeName ? 1 : 0);
onSelect(newValue, updatedPosition);
}, [contextData, logicalParts]);
useEffect(() => {
if (!anchorEl.current) {
setOffsetPos({ top: 0, left: 0 });
return;
}
const element = anchorEl.current.querySelector("textarea") || anchorEl.current;
const style = window.getComputedStyle(element);
const fontSize = `${style.getPropertyValue("font-size")}`;
const fontFamily = `${style.getPropertyValue("font-family")}`;
const lineHeight = parseInt(`${style.getPropertyValue("line-height")}`);
const span = document.createElement("div");
span.style.font = `${fontSize} ${fontFamily}`;
span.style.padding = style.getPropertyValue("padding");
span.style.lineHeight = `${lineHeight}px`;
span.style.width = `${element.offsetWidth}px`;
span.style.maxWidth = `${element.offsetWidth}px`;
span.style.whiteSpace = style.getPropertyValue("white-space");
span.style.overflowWrap = style.getPropertyValue("overflow-wrap");
const marker = document.createElement("span");
span.appendChild(document.createTextNode(fullValue.valueBeforeCursor || ""));
span.appendChild(marker);
span.appendChild(document.createTextNode(fullValue.valueAfterCursor || ""));
document.body.appendChild(span);
const spanRect = span.getBoundingClientRect();
const markerRect = marker.getBoundingClientRect();
const leftOffset = markerRect.left - spanRect.left;
const topOffset = markerRect.bottom - spanRect.bottom - (hasHelperText ? lineHeight : 0);
setOffsetPos({ top: topOffset, left: leftOffset });
span.remove();
marker.remove();
}, [anchorEl, caretPosition, hasHelperText, fullValue]);
return (
<>
<Autocomplete
loading={loading}
disabledFullScreen
value={contextData?.valueContext || ""}
options={options}
anchor={anchorEl}
minLength={0}
offset={offsetPos}
onSelect={handleSelect}
onFoundOptions={onFoundOptions}
maxDisplayResults={{
limit: AUTOCOMPLETE_LIMITS.displayResults,
message: "Please, specify the query more precisely."
}}
/>
</>
);
};
export default LogsQueryEditorAutocomplete;

View File

@@ -0,0 +1,117 @@
import { ContextData, ContextType, LogicalPart, LogicalPartPosition, LogicalPartType } from "./types";
import { pipeList } from "./pipes";
const BUILDER_OPERATORS = ["AND", "OR", "NOT"];
const PIPE_NAMES = pipeList.map(p => p.value);
export const splitLogicalParts = (expr: string) => {
// Replace spaces around the colon (:) with just the colon, removing the spaces
const input = expr; //.replace(/\s*:\s*/g, ":");
const parts: LogicalPart[] = [];
let currentPart = "";
let isPipePart = false;
const quotes = ["'", "\"", "`"];
let insideQuotes = false;
let expectedQuote = "";
const openBrackets = ["(", "[", "{"];
const closeBrackets = [")", "]", "}"];
const brackets = [...openBrackets, ...closeBrackets];
let insideBrackets = 0;
let startIndex = 0;
for (let i = 0; i < input.length; i++) {
const char = input[i];
// Check if the current character is a quote
if (quotes.includes(char)) {
const isClosedQuote: boolean = insideQuotes && (char === expectedQuote);
insideQuotes = !isClosedQuote;
expectedQuote = isClosedQuote ? "" : char;
}
// Check if the current character is a bracket
if (!insideQuotes && brackets.includes(char)) {
const dir = openBrackets.includes(char) ? 1 : -1;
insideBrackets += dir;
}
// Check if the current character is a pipe
if ((!insideQuotes && !insideBrackets && char === "|")) {
isPipePart = true;
const countStartSpaces = currentPart.match(/^ */)?.[0].length || 0;
const countEndSpaces = currentPart.match(/ *$/)?.[0].length || 0;
pushPart(currentPart, true, [startIndex + countStartSpaces, i - countEndSpaces - 1], parts);
currentPart = "";
startIndex = i + 1;
continue;
}
// Check if the current character is a space
if (!isPipePart && !insideQuotes && !insideBrackets && char === " ") {
const nextStr = input.slice(i).replace(/^\s*/, "");
const prevStr = input.slice(0, i).replace(/\s*$/, "");
if (!nextStr.startsWith(":") && !prevStr.endsWith(":")) {
pushPart(currentPart, false, [startIndex, i - 1], parts);
currentPart = "";
startIndex = i + 1;
continue;
}
}
currentPart += char;
}
// push the last part
pushPart(currentPart, isPipePart, [startIndex, input.length], parts);
return parts;
};
const pushPart = (currentPart: string, isPipePart: boolean, position: LogicalPartPosition, parts: LogicalPart[]) => {
const trimmedPart = currentPart.trim();
if (!trimmedPart) return;
const isOperator = BUILDER_OPERATORS.includes(trimmedPart.toUpperCase());
parts.push({
id: parts.length,
value: trimmedPart,
position,
type: isPipePart
? LogicalPartType.Pipe
: isOperator ? LogicalPartType.Operator : LogicalPartType.Filter,
});
};
export const getContextData = (part: LogicalPart, cursorPos: number) => {
const valueBeforeCursor = part.value.substring(0, cursorPos);
const valueAfterCursor = part.value.substring(cursorPos);
const metaData: ContextData = {
valueBeforeCursor,
valueAfterCursor,
valueContext: part.value,
contextType: ContextType.Unknown,
};
if (part.type === LogicalPartType.Filter) {
const noColon = !valueBeforeCursor.includes(":") && !valueAfterCursor.includes(":");
if (noColon) {
metaData.contextType = ContextType.FilterUnknown;
} else if (valueBeforeCursor.includes(":")) {
const [filterName, filterValue] = valueBeforeCursor.split(":");
metaData.contextType = ContextType.FilterValue;
metaData.filterName = filterName;
metaData.valueContext = filterValue;
} else {
metaData.contextType = ContextType.FilterName;
}
} else if (part.type === LogicalPartType.Pipe) {
const valueStartWithPipe = PIPE_NAMES.some(p => part.value.startsWith(p));
metaData.contextType = valueStartWithPipe ? ContextType.PipeValue : ContextType.PipeName;
}
metaData.valueContext = metaData.valueContext.replace(/^["']|["']$/g, "");
return metaData;
};

View File

@@ -0,0 +1,130 @@
import React from "react";
import { ContextType } from "./types";
import { FunctionIcon } from "../../../Main/Icons";
const docsUrl = "https://docs.victoriametrics.com/victorialogs/logsql";
const classLink = "vm-link vm-link_colored";
const prepareDescription = (text: string): string => {
const replaceClass = `$1 target="_blank" class="${classLink}" $2`;
const replaceHref = `$1 $2${docsUrl}#`;
return text
.replace(/(<a) (href=")#/gm, replaceHref)
.replace(/(<a) (href="[^"]+")/gm, replaceClass);
};
export const pipeList = [
{
"value": "copy",
"description": "<a href=\"#copy-pipe\"><code>copy</code></a> copies <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "delete",
"description": "<a href=\"#delete-pipe\"><code>delete</code></a> deletes <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "drop_empty_fields",
"description": "<a href=\"#drop_empty_fields-pipe\"><code>drop_empty_fields</code></a> drops <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a> with empty values."
},
{
"value": "extract",
"description": "<a href=\"#extract-pipe\"><code>extract</code></a> extracts the specified text into the given log fields."
},
{
"value": "extract_regexp",
"description": "<a href=\"#extract_regexp-pipe\"><code>extract_regexp</code></a> extracts the specified text into the given log fields via <a href=\"https://github.com/google/re2/wiki/Syntax\" rel=\"external\" target=\"_blank\">RE2 regular expressions</a>."
},
{
"value": "field_names",
"description": "<a href=\"#field_names-pipe\"><code>field_names</code></a> returns all the names of <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "field_values",
"description": "<a href=\"#field_values-pipe\"><code>field_values</code></a> returns all the values for the given <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log field</a>."
},
{
"value": "fields",
"description": "<a href=\"#fields-pipe\"><code>fields</code></a> selects the given set of <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "filter",
"description": "<a href=\"#filter-pipe\"><code>filter</code></a> applies additional <a href=\"#filters\">filters</a> to results."
},
{
"value": "format",
"description": "<a href=\"#format-pipe\"><code>format</code></a> formats output field from input <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "limit",
"description": "<a href=\"#limit-pipe\"><code>limit</code></a> limits the number selected logs."
},
{
"value": "math",
"description": "<a href=\"#math-pipe\"><code>math</code></a> performs mathematical calculations over <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "offset",
"description": "<a href=\"#offset-pipe\"><code>offset</code></a> skips the given number of selected logs."
},
{
"value": "pack_json",
"description": "<a href=\"#pack_json-pipe\"><code>pack_json</code></a> packs <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a> into JSON object."
},
{
"value": "pack_logfmt",
"description": "<a href=\"#pack_logfmt-pipe\"><code>pack_logfmt</code></a> packs <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a> into <a href=\"https://brandur.org/logfmt\" rel=\"external\" target=\"_blank\">logfmt</a> message."
},
{
"value": "rename",
"description": "<a href=\"#rename-pipe\"><code>rename</code></a> renames <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "replace",
"description": "<a href=\"#replace-pipe\"><code>replace</code></a> replaces substrings in the specified <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "replace_regexp",
"description": "<a href=\"#replace_regexp-pipe\"><code>replace_regexp</code></a> updates <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a> with regular expressions."
},
{
"value": "sort",
"description": "<a href=\"#sort-pipe\"><code>sort</code></a> sorts logs by the given <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">fields</a>."
},
{
"value": "stats",
"description": "<a href=\"#stats-pipe\"><code>stats</code></a> calculates various stats over the selected logs."
},
{
"value": "stream_context",
"description": "<a href=\"#stream_context-pipe\"><code>stream_context</code></a> allows selecting surrounding logs in front and after the matching logs\nper each <a href=\"/victorialogs/keyconcepts/#stream-fields\">log stream</a>."
},
{
"value": "top",
"description": "<a href=\"#top-pipe\"><code>top</code></a> returns top <code>N</code> field sets with the maximum number of matching logs."
},
{
"value": "uniq",
"description": "<a href=\"#uniq-pipe\"><code>uniq</code></a> returns unique log entires."
},
{
"value": "unpack_json",
"description": "<a href=\"#unpack_json-pipe\"><code>unpack_json</code></a> unpacks JSON messages from <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "unpack_logfmt",
"description": "<a href=\"#unpack_logfmt-pipe\"><code>unpack_logfmt</code></a> unpacks <a href=\"https://brandur.org/logfmt\" rel=\"external\" target=\"_blank\">logfmt</a> messages from <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "unpack_syslog",
"description": "<a href=\"#unpack_syslog-pipe\"><code>unpack_syslog</code></a> unpacks <a href=\"https://en.wikipedia.org/wiki/Syslog\" rel=\"external\" target=\"_blank\">syslog</a> messages from <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
},
{
"value": "unroll",
"description": "<a href=\"#unroll-pipe\"><code>unroll</code></a> unrolls JSON arrays from <a href=\"https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model\">log fields</a>."
}
].map(item => ({
...item,
type: ContextType.PipeName,
icon: <FunctionIcon/>,
description: prepareDescription(item.description),
}));

View File

@@ -0,0 +1,31 @@
export enum LogicalPartType {
Filter = "Filter",
Pipe = "Pipe",
Operator = "Operator",
}
export type LogicalPartPosition = [start: number, end: number];
export interface LogicalPart {
id: number;
value: string;
type: LogicalPartType;
position: LogicalPartPosition;
}
export interface ContextData {
valueBeforeCursor: string;
valueAfterCursor: string;
contextType: ContextType;
valueContext: string;
filterName?: string;
}
export enum ContextType {
FilterName = "FilterName",
FilterUnknown = "FilterUnknown",
FilterValue = "FilterValue",
PipeName = "Pipes",
PipeValue = "PipeValue",
Unknown = "Unknown",
}

View File

@@ -0,0 +1,137 @@
import React, { useEffect, useState, useRef, Dispatch, SetStateAction } from "preact/compat";
import dayjs from "dayjs";
import { ContextData, ContextType } from "./types";
import { FunctionIcon, LabelIcon, MetricIcon, ValueIcon } from "../../../Main/Icons";
import { AutocompleteOptions } from "../../../Main/Autocomplete/Autocomplete";
import { useAppState } from "../../../../state/common/StateContext";
import { useTimeState } from "../../../../state/time/TimeStateContext";
import { useCallback } from "react";
import { AUTOCOMPLETE_LIMITS } from "../../../../constants/queryAutocomplete";
import { LogsFiledValues } from "../../../../api/types";
import { useLogsDispatch, useLogsState } from "../../../../state/logsPanel/LogsStateContext";
type FetchDataArgs = {
urlSuffix: string;
setter: Dispatch<SetStateAction<AutocompleteOptions[]>>
type: ContextType;
params?: URLSearchParams;
}
const icons = {
[ContextType.FilterName]: <MetricIcon/>,
[ContextType.FilterUnknown]: <MetricIcon/>,
[ContextType.FilterValue]: <ValueIcon/>,
[ContextType.PipeName]: <FunctionIcon/>,
[ContextType.PipeValue]: <LabelIcon/>,
[ContextType.Unknown]: <ValueIcon/>
};
export const useFetchLogsQLOptions = (contextData?: ContextData) => {
const { serverUrl } = useAppState();
const { period: { start, end } } = useTimeState();
const { autocompleteCache } = useLogsState();
const dispatch = useLogsDispatch();
const [loading, setLoading] = useState(false);
const [fieldNames, setFieldNames] = useState<AutocompleteOptions[]>([]);
const [fieldValues, setFieldValues] = useState<AutocompleteOptions[]>([]);
const abortControllerRef = useRef(new AbortController());
const getQueryParams = useCallback((params?: Record<string, string>) => {
const startDay = dayjs(start * 1000).startOf("day").valueOf() / 1000;
const endDay = dayjs(end * 1000).endOf("day").valueOf() / 1000;
return new URLSearchParams({
...(params || {}),
limit: `${AUTOCOMPLETE_LIMITS.queryLimit}`,
start: `${startDay}`,
end: `${endDay}`
});
}, [start, end]);
const processData = (values: LogsFiledValues[], type: ContextType): AutocompleteOptions[] => {
return values.map(v => ({
value: v.value,
type: `${type}`,
icon: icons[type]
}));
};
const fetchData = async ({ urlSuffix, setter, type, params }: FetchDataArgs) => {
// if (!value && type === TypeData.metric) return;
abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
const { signal } = abortControllerRef.current;
const key = `${urlSuffix}?${params?.toString()}`;
setLoading(true);
try {
const cachedData = autocompleteCache.get(key);
if (cachedData) {
setter(processData(cachedData, type));
setLoading(false);
return;
}
const response = await fetch(`${serverUrl}/select/logsql/${urlSuffix}?${params}`, { signal });
if (response.ok) {
const data = await response.json();
const value = (data?.values || []) as LogsFiledValues[];
setter(value ? processData(value, type) : []);
dispatch({ type: "SET_AUTOCOMPLETE_CACHE", payload: { key, value } });
}
setLoading(false);
} catch (e) {
if (e instanceof Error && e.name !== "AbortError") {
dispatch({ type: "SET_AUTOCOMPLETE_CACHE", payload: { key, value: [] } });
setLoading(false);
console.error(e);
}
}
};
// fetch field names
useEffect(() => {
const validContexts = [ContextType.FilterName, ContextType.FilterUnknown];
const isInvalidContext = !validContexts.includes(contextData?.contextType || ContextType.Unknown);
if (!serverUrl || isInvalidContext) {
return;
}
setFieldNames([]);
fetchData({
urlSuffix: "field_names",
setter: setFieldNames,
type: ContextType.FilterName,
params: getQueryParams({ query: "*" })
});
return () => abortControllerRef.current?.abort();
}, [serverUrl, contextData]);
// fetch field values
useEffect(() => {
const isInvalidContext = contextData?.contextType !== ContextType.FilterValue;
if (!serverUrl || isInvalidContext || !contextData?.filterName) {
return;
}
setFieldValues([]);
fetchData({
urlSuffix: "field_values",
setter: setFieldValues,
type: ContextType.FilterValue,
params: getQueryParams({ query: "*", field: contextData.filterName })
});
return () => abortControllerRef.current?.abort();
}, [serverUrl, contextData]);
return {
fieldNames,
fieldValues,
loading,
};
};

View File

@@ -2,7 +2,6 @@ import React, { FC, useEffect, useRef, useState } from "preact/compat";
import { KeyboardEvent } from "react";
import { ErrorTypes } from "../../../types";
import TextField from "../../Main/TextField/TextField";
import QueryEditorAutocomplete from "./QueryEditorAutocomplete";
import "./style.scss";
import { QueryStats } from "../../../api/types";
import { partialWarning, seriesFetchedWarning } from "./warningText";
@@ -11,6 +10,16 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
import { useQueryState } from "../../../state/query/QueryStateContext";
import debounce from "lodash.debounce";
export interface QueryEditorAutocompleteProps {
value: string;
anchorEl: React.RefObject<HTMLInputElement>;
caretPosition: [number, number]; // [start, end]
hasHelperText: boolean;
includeFunctions: boolean;
onSelect: (val: string, caretPosition: number) => void;
onFoundOptions: (val: AutocompleteOptions[]) => void;
}
export interface QueryEditorProps {
onChange: (query: string) => void;
onEnter: () => void;
@@ -19,6 +28,7 @@ export interface QueryEditorProps {
value: string;
oneLiner?: boolean;
autocomplete: boolean;
autocompleteEl?: FC<QueryEditorAutocompleteProps>;
error?: ErrorTypes | string;
stats?: QueryStats;
label: string;
@@ -33,6 +43,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
onArrowUp,
onArrowDown,
autocomplete,
autocompleteEl: AutocompleteEl,
error,
stats,
label,
@@ -47,7 +58,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
const [caretPositionInput, setCaretPositionInput] = useState<[number, number]>([0, 0]);
const autocompleteAnchorEl = useRef<HTMLInputElement>(null);
const [showAutocomplete, setShowAutocomplete] = useState(autocomplete);
const [showAutocomplete, setShowAutocomplete] = useState(!!AutocompleteEl);
const debouncedSetShowAutocomplete = useRef(debounce(setShowAutocomplete, 500)).current;
const warning = [
@@ -113,7 +124,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
};
useEffect(() => {
setOpenAutocomplete(autocomplete);
setOpenAutocomplete(!!AutocompleteEl);
}, [autocompleteQuick]);
useEffect(() => {
@@ -140,8 +151,8 @@ const QueryEditor: FC<QueryEditorProps> = ({
inputmode={"search"}
caretPosition={caretPositionInput}
/>
{showAutocomplete && autocomplete && (
<QueryEditorAutocomplete
{showAutocomplete && autocomplete && AutocompleteEl && (
<AutocompleteEl
value={value}
anchorEl={autocompleteAnchorEl}
caretPosition={caretPositionAutocomplete}

View File

@@ -1,20 +1,11 @@
import React, { FC, useState, useEffect, useMemo, useCallback } from "preact/compat";
import Autocomplete, { AutocompleteOptions } from "../../Main/Autocomplete/Autocomplete";
import Autocomplete from "../../Main/Autocomplete/Autocomplete";
import { useFetchQueryOptions } from "../../../hooks/useFetchQueryOptions";
import { escapeRegexp, hasUnclosedQuotes } from "../../../utils/regexp";
import useGetMetricsQL from "../../../hooks/useGetMetricsQL";
import { QueryContextType } from "../../../types";
import { AUTOCOMPLETE_LIMITS } from "../../../constants/queryAutocomplete";
interface QueryEditorAutocompleteProps {
value: string;
anchorEl: React.RefObject<HTMLElement>;
caretPosition: [number, number]; // [start, end]
hasHelperText: boolean;
includeFunctions: boolean;
onSelect: (val: string, caretPosition: number) => void;
onFoundOptions: (val: AutocompleteOptions[]) => void;
}
import { QueryEditorAutocompleteProps } from "./QueryEditor";
const QueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
value,

View File

@@ -0,0 +1,246 @@
import React, { FC, useMemo, useState } from "preact/compat";
import useBoolean from "../../../hooks/useBoolean";
import { RestartIcon, SettingsIcon } from "../../Main/Icons";
import Button from "../../Main/Button/Button";
import Modal from "../../Main/Modal/Modal";
import Tooltip from "../../Main/Tooltip/Tooltip";
import { Logs } from "../../../api/types";
import Select from "../../Main/Select/Select";
import { useSearchParams } from "react-router-dom";
import "./style.scss";
import Switch from "../../Main/Switch/Switch";
import TextField from "../../Main/TextField/TextField";
import dayjs from "dayjs";
import Hyperlink from "../../Main/Hyperlink/Hyperlink";
import {
LOGS_DISPLAY_FIELDS,
LOGS_GROUP_BY,
LOGS_DATE_FORMAT,
LOGS_URL_PARAMS,
WITHOUT_GROUPING
} from "../../../constants/logs";
const {
GROUP_BY,
NO_WRAP_LINES,
COMPACT_GROUP_HEADER,
DISPLAY_FIELDS,
DATE_FORMAT
} = LOGS_URL_PARAMS;
const title = "Group view settings";
interface Props {
logs: Logs[];
}
const GroupLogsConfigurators: FC<Props> = ({ logs }) => {
const [searchParams, setSearchParams] = useSearchParams();
const groupBy = searchParams.get(GROUP_BY) || LOGS_GROUP_BY;
const noWrapLines = searchParams.get(NO_WRAP_LINES) === "true";
const compactGroupHeader = searchParams.get(COMPACT_GROUP_HEADER) === "true";
const displayFieldsString = searchParams.get(DISPLAY_FIELDS) || "";
const displayFields = displayFieldsString ? displayFieldsString.split(",") : [];
const [dateFormat, setDateFormat] = useState(searchParams.get(DATE_FORMAT) || LOGS_DATE_FORMAT);
const [errorFormat, setErrorFormat] = useState("");
const isGroupChanged = groupBy !== LOGS_GROUP_BY;
const isDisplayFieldsChanged = displayFields.length > 0;
const isTimeChanged = searchParams.get(DATE_FORMAT) !== LOGS_DATE_FORMAT;
const hasChanges = [
isGroupChanged,
isDisplayFieldsChanged,
noWrapLines,
compactGroupHeader,
isTimeChanged
].some(Boolean);
const logsKeys = useMemo(() => {
const excludeKeys = ["_msg", "_time"];
const uniqKeys = Array.from(new Set(logs.map(l => Object.keys(l)).flat()));
return uniqKeys.filter(k => !excludeKeys.includes(k));
}, [logs]);
const {
value: openModal,
toggle: toggleOpen,
setFalse: handleClose,
} = useBoolean(false);
const handleSelectGroupBy = (key: string) => {
searchParams.set(GROUP_BY, key);
setSearchParams(searchParams);
};
const handleSelectDisplayField = (value: string) => {
const prev = displayFields;
const newDisplayFields = prev.includes(value) ? prev.filter(v => v !== value) : [...prev, value];
searchParams.set(DISPLAY_FIELDS, newDisplayFields.join(","));
setSearchParams(searchParams);
};
const handleResetDisplayFields = () => {
searchParams.delete(DISPLAY_FIELDS);
setSearchParams(searchParams);
};
const toggleWrapLines = () => {
searchParams.set(NO_WRAP_LINES, String(!noWrapLines));
setSearchParams(searchParams);
};
const toggleCompactGroupHeader = () => {
searchParams.set(COMPACT_GROUP_HEADER, String(!compactGroupHeader));
setSearchParams(searchParams);
};
const handleChangeDateFormat = (format: string) => {
const date = new Date();
if (!dayjs(date, format, true).isValid()) {
setErrorFormat("Invalid date format");
}
setDateFormat(format);
};
const handleSaveAndClose = () => {
searchParams.set(DATE_FORMAT, dateFormat);
setSearchParams(searchParams);
handleClose();
};
const tooltipContent = () => {
if (!hasChanges) return title;
return (
<div className="vm-group-logs-configurator__tooltip">
<p>{title}</p>
<hr/>
<ul>
{isGroupChanged && <li>Group by <code>{`"${groupBy}"`}</code></li>}
{isDisplayFieldsChanged && <li>Display fields: {displayFields.length || 1}</li>}
{noWrapLines && <li>Single-line text is enabled</li>}
{compactGroupHeader && <li>Compact group header is enabled</li>}
{isTimeChanged && <li>Date format: <code>{dateFormat}</code></li>}
</ul>
</div>
);
};
return (
<>
<div className="vm-group-logs-configurator-button">
<Tooltip title={tooltipContent()}>
<Button
variant="text"
startIcon={<SettingsIcon/>}
onClick={toggleOpen}
ariaLabel={title}
/>
</Tooltip>
{hasChanges && <span className="vm-group-logs-configurator-button__marker"/>}
</div>
{openModal && (
<Modal
title={title}
onClose={handleSaveAndClose}
>
<div className="vm-group-logs-configurator">
<div className="vm-group-logs-configurator-item">
<Select
value={groupBy}
list={[WITHOUT_GROUPING, ...logsKeys]}
label="Group by field"
placeholder="Group by field"
onChange={handleSelectGroupBy}
searchable
/>
<Tooltip title={"Reset grouping"}>
<Button
variant="text"
color="primary"
startIcon={<RestartIcon/>}
onClick={() => handleSelectGroupBy(LOGS_GROUP_BY)}
/>
</Tooltip>
<span className="vm-group-logs-configurator-item__info">
Select a field to group logs by (default: <code>{LOGS_GROUP_BY}</code>).
</span>
</div>
<div className="vm-group-logs-configurator-item">
<Select
value={displayFields}
list={logsKeys}
label="Display fields"
placeholder="Display fields"
onChange={handleSelectDisplayField}
searchable
/>
<Tooltip title={"Clear fields"}>
<Button
variant="text"
color="primary"
startIcon={<RestartIcon/>}
onClick={handleResetDisplayFields}
/>
</Tooltip>
<span className="vm-group-logs-configurator-item__info">
Select fields to display instead of the message (default: <code>{LOGS_DISPLAY_FIELDS}</code>).
</span>
</div>
<div className="vm-group-logs-configurator-item">
<TextField
autofocus
label="Date format"
value={dateFormat}
onChange={handleChangeDateFormat}
error={errorFormat}
/>
<Tooltip title={"Reset format"}>
<Button
variant="text"
color="primary"
startIcon={<RestartIcon/>}
onClick={() => setDateFormat(LOGS_DATE_FORMAT)}
/>
</Tooltip>
<span className="vm-group-logs-configurator-item__info vm-group-logs-configurator-item__info_input">
Set the date format (e.g., <code>YYYY-MM-DD HH:mm:ss</code>).
Learn more in <Hyperlink
href="https://day.js.org/docs/en/display/format"
>this documentation</Hyperlink>. <br/>
Your current date format: <code>{dayjs().format(dateFormat || LOGS_DATE_FORMAT)}</code>
</span>
</div>
<div className="vm-group-logs-configurator-item">
<Switch
value={noWrapLines}
onChange={toggleWrapLines}
label="Single-line message"
/>
<span className="vm-group-logs-configurator-item__info">
Displays message in a single line and truncates it with an ellipsis if it exceeds the available space
</span>
</div>
<div className="vm-group-logs-configurator-item">
<Switch
value={compactGroupHeader}
onChange={toggleCompactGroupHeader}
label="Compact group header"
/>
<span className="vm-group-logs-configurator-item__info">
Shows group headers in one line with a &quot;+N more&quot; badge for extra fields.
</span>
</div>
</div>
</Modal>
)}
</>
);
};
export default GroupLogsConfigurators;

View File

@@ -0,0 +1,48 @@
@use "src/styles/variables" as *;
.vm-group-logs-configurator {
display: grid;
gap: calc($padding-large * 2);
padding: $padding-global 0;
width: 600px;
&-item {
display: grid;
grid-template-columns: 1fr 31px;
align-items: center;
justify-content: stretch;
gap: 0 $padding-small;
&__info {
margin-top: $padding-small;
grid-column: 1/span 2;
font-size: $font-size-small;
color: $color-text-secondary;
line-height: 130%;
&_input {
margin-top: 0;
}
}
}
&-button {
position: relative;
&__marker {
position: absolute;
top: 6px;
left: 6px;
width: 5px;
height: 5px;
border-radius: 50%;
background-color: $color-secondary;
}
}
&__tooltip {
ul {
list-style-position: inside;
}
}
}

View File

@@ -30,6 +30,10 @@ const Accordion: FC<AccordionProps> = ({
onChange && onChange(isOpen);
}, [isOpen]);
useEffect(() => {
setIsOpen(defaultExpanded);
}, [defaultExpanded]);
return (
<>
<header

View File

@@ -28,7 +28,7 @@ interface AutocompleteProps {
offset?: {top: number, left: number}
maxDisplayResults?: {limit: number, message?: string}
loading?: boolean;
onSelect: (val: string) => void
onSelect: (val: string, item: AutocompleteOptions) => void
onOpenAutocomplete?: (val: boolean) => void
onFoundOptions?: (val: AutocompleteOptions[]) => void
onChangeWrapperRef?: (elementRef: React.RefObject<HTMLElement>) => void
@@ -97,9 +97,9 @@ const Autocomplete: FC<AutocompleteProps> = ({
return noOptionsText && !foundOptions.length;
}, [noOptionsText,foundOptions]);
const createHandlerSelect = (item: string) => () => {
const createHandlerSelect = (item: AutocompleteOptions) => () => {
if (disabled) return;
onSelect(item);
onSelect(item.value, item);
if (!selected) handleCloseAutocomplete();
};
@@ -141,7 +141,7 @@ const Autocomplete: FC<AutocompleteProps> = ({
if (key === "Enter") {
const item = foundOptions[focusOption.index];
item && onSelect(item.value);
item && onSelect(item.value, item);
if (!selected) handleCloseAutocomplete();
}
@@ -206,7 +206,7 @@ const Autocomplete: FC<AutocompleteProps> = ({
})}
id={`$autocomplete$${option.value}`}
key={`${i}${option.value}`}
onClick={createHandlerSelect(option.value)}
onClick={createHandlerSelect(option)}
onMouseEnter={createHandlerMouseEnter(i)}
onMouseLeave={handlerMouseLeave}
>

View File

@@ -67,11 +67,11 @@ const Modal: FC<ModalProps> = ({
})}
onMouseDown={onClose}
>
<div className="vm-modal-content">
<div
className="vm-modal-content-header"
onMouseDown={handleMouseDown}
>
<div
className="vm-modal-content"
onMouseDown={handleMouseDown}
>
<div className="vm-modal-content-header">
{title && (
<div className="vm-modal-content-header__title">
{title}
@@ -91,7 +91,6 @@ const Modal: FC<ModalProps> = ({
{/* tabIndex to fix Ctrl-A */}
<div
className="vm-modal-content-body"
onMouseDown={handleMouseDown}
tabIndex={0}
>
{children}

View File

@@ -33,9 +33,9 @@
align-items: center;
justify-content: center;
background-color: $color-hover-black;
padding: 2px 2px 2px 6px;
padding: 2px 2px 2px $padding-small;
border-radius: $border-radius-small;
font-size: $font-size;
font-size: $font-size-small;
line-height: $font-size;
max-width: 100%;

View File

@@ -1,2 +1,21 @@
import { DATE_TIME_FORMAT } from "./date";
export const LOGS_ENTRIES_LIMIT = 50;
export const LOGS_BARS_VIEW = 100;
// "Ungrouped" is a string that is used as a value for the "groupBy" parameter.
export const WITHOUT_GROUPING = "Ungrouped";
// Default values for the logs configurators.
export const LOGS_GROUP_BY = "_stream";
export const LOGS_DISPLAY_FIELDS = "_msg";
export const LOGS_DATE_FORMAT = `${DATE_TIME_FORMAT}.SSS`;
// URL parameters for the logs page.
export const LOGS_URL_PARAMS = {
GROUP_BY: "groupBy",
DISPLAY_FIELDS: "displayFields",
NO_WRAP_LINES: "noWrapLines",
COMPACT_GROUP_HEADER: "compactGroupHeader",
DATE_FORMAT: "dateFormat",
};

View File

@@ -20,7 +20,7 @@ const useClickOutside = <T extends HTMLElement = HTMLElement>(
handler(event); // Call the handler only if the click is outside of the element passed.
}, [ref, handler]);
useEventListener("mousedown", listener);
useEventListener("mouseup", listener);
useEventListener("touchstart", listener);
};

View File

@@ -25,6 +25,7 @@ import { QueryStats } from "../../../api/types";
import { usePrettifyQuery } from "./hooks/usePrettifyQuery";
import QueryHistory from "../QueryHistory/QueryHistory";
import AnomalyConfig from "../../../components/ExploreAnomaly/AnomalyConfig";
import QueryEditorAutocomplete from "../../../components/Configurators/QueryEditor/QueryEditorAutocomplete";
export interface QueryConfiguratorProps {
queryErrors: string[];
@@ -216,6 +217,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
<QueryEditor
value={stateQuery[i]}
autocomplete={!hideButtons?.autocomplete && (autocomplete || autocompleteQuick)}
autocompleteEl={QueryEditorAutocomplete}
error={queryErrors[i]}
stats={stats[i]}
onArrowUp={createHandlerArrow(-1, i)}

View File

@@ -6,6 +6,9 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
import Button from "../../../components/Main/Button/Button";
import QueryEditor from "../../../components/Configurators/QueryEditor/QueryEditor";
import TextField from "../../../components/Main/TextField/TextField";
import LogsQueryEditorAutocomplete from "../../../components/Configurators/QueryEditor/LogsQL/LogsQueryEditorAutocomplete";
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
import Switch from "../../../components/Main/Switch/Switch";
export interface ExploreLogHeaderProps {
query: string;
@@ -27,6 +30,8 @@ const ExploreLogsHeader: FC<ExploreLogHeaderProps> = ({
onRun,
}) => {
const { isMobile } = useDeviceDetect();
const { autocomplete } = useQueryState();
const queryDispatch = useQueryDispatch();
const [errorLimit, setErrorLimit] = useState("");
const [limitInput, setLimitInput] = useState(limit);
@@ -42,6 +47,10 @@ const ExploreLogsHeader: FC<ExploreLogHeaderProps> = ({
}
};
const onChangeAutocomplete = () => {
queryDispatch({ type: "TOGGLE_AUTOCOMPLETE" });
};
useEffect(() => {
setLimitInput(limit);
}, [limit]);
@@ -57,7 +66,8 @@ const ExploreLogsHeader: FC<ExploreLogHeaderProps> = ({
<div className="vm-explore-logs-header-top">
<QueryEditor
value={query}
autocomplete={false}
autocomplete={autocomplete}
autocompleteEl={LogsQueryEditorAutocomplete}
onArrowUp={() => null}
onArrowDown={() => null}
onEnter={onRun}
@@ -75,7 +85,14 @@ const ExploreLogsHeader: FC<ExploreLogHeaderProps> = ({
/>
</div>
<div className="vm-explore-logs-header-bottom">
<div className="vm-explore-logs-header-bottom-contols"></div>
<div className="vm-explore-logs-header-bottom-contols">
<Switch
label={"Autocomplete"}
value={autocomplete}
onChange={onChangeAutocomplete}
fullWidth={isMobile}
/>
</div>
<div className="vm-explore-logs-header-bottom-helpful">
<a
className="vm-link vm-link_with-icon"

View File

@@ -26,6 +26,9 @@
}
&-contols {
display: flex;
align-items: center;
justify-content: flex-start;
flex-grow: 1;
}

View File

@@ -1,24 +1,19 @@
import React, { FC, useCallback, useEffect, useMemo, useRef } from "preact/compat";
import { MouseEvent, useState } from "react";
import React, { FC, useCallback, useEffect, useMemo } from "preact/compat";
import { useState } from "react";
import "./style.scss";
import { Logs } from "../../../api/types";
import Accordion from "../../../components/Main/Accordion/Accordion";
import { groupByMultipleKeys } from "../../../utils/array";
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
import GroupLogsItem from "./GroupLogsItem";
import { useAppState } from "../../../state/common/StateContext";
import classNames from "classnames";
import Button from "../../../components/Main/Button/Button";
import { CollapseIcon, ExpandIcon, StorageIcon } from "../../../components/Main/Icons";
import Popper from "../../../components/Main/Popper/Popper";
import TextField from "../../../components/Main/TextField/TextField";
import useBoolean from "../../../hooks/useBoolean";
import useStateSearchParams from "../../../hooks/useStateSearchParams";
import { CollapseIcon, ExpandIcon } from "../../../components/Main/Icons";
import { useSearchParams } from "react-router-dom";
import { getStreamPairs } from "../../../utils/logs";
const WITHOUT_GROUPING = "No Grouping";
import GroupLogsConfigurators
from "../../../components/LogsConfigurators/GroupLogsConfigurators/GroupLogsConfigurators";
import GroupLogsHeader from "./GroupLogsHeader";
import { LOGS_DISPLAY_FIELDS, LOGS_GROUP_BY, LOGS_URL_PARAMS, WITHOUT_GROUPING } from "../../../constants/logs";
interface Props {
logs: Logs[];
@@ -26,47 +21,22 @@ interface Props {
}
const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
const { isDarkTheme } = useAppState();
const copyToClipboard = useCopyToClipboard();
const [searchParams, setSearchParams] = useSearchParams();
const [searchParams] = useSearchParams();
const [expandGroups, setExpandGroups] = useState<boolean[]>([]);
const [groupBy, setGroupBy] = useStateSearchParams("_stream", "groupBy");
const [copied, setCopied] = useState<string | null>(null);
const [searchKey, setSearchKey] = useState("");
const optionsButtonRef = useRef<HTMLDivElement>(null);
const {
value: openOptions,
toggle: toggleOpenOptions,
setFalse: handleCloseOptions,
} = useBoolean(false);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const displayFieldsString = searchParams.get(LOGS_URL_PARAMS.DISPLAY_FIELDS) || LOGS_DISPLAY_FIELDS;
const displayFields = displayFieldsString.split(",");
const expandAll = useMemo(() => expandGroups.every(Boolean), [expandGroups]);
const logsKeys = useMemo(() => {
const excludeKeys = ["_msg", "_time"];
const uniqKeys = Array.from(new Set(logs.map(l => Object.keys(l)).flat()));
return [WITHOUT_GROUPING, ...uniqKeys.filter(k => !excludeKeys.includes(k))];
}, [logs]);
const filteredLogsKeys = useMemo(() => {
if (!searchKey) return logsKeys;
try {
const regexp = new RegExp(searchKey, "i");
return logsKeys.filter(item => regexp.test(item))
.sort((a, b) => (a.match(regexp)?.index || 0) - (b.match(regexp)?.index || 0));
} catch (e) {
return [];
}
}, [logsKeys, searchKey]);
const groupData = useMemo(() => {
return groupByMultipleKeys(logs, [groupBy]).map((item) => {
const streamValue = item.values[0]?.[groupBy] || "";
const pairs = getStreamPairs(streamValue);
// values sorting by time
const values = item.values.sort((a,b) => new Date(b._time).getTime() - new Date(a._time).getTime());
const values = item.values.sort((a, b) => new Date(b._time).getTime() - new Date(a._time).getTime());
return {
keys: item.keys,
keysString: item.keys.join(""),
@@ -76,23 +46,6 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
}).sort((a, b) => a.keysString.localeCompare(b.keysString)); // groups sorting
}, [logs, groupBy]);
const handleClickByPair = (value: string) => async (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
const isKeyValue = /(.+)?=(".+")/.test(value);
const copyValue = isKeyValue ? `${value.replace(/=/, ": ")}` : `${groupBy}: "${value}"`;
const isCopied = await copyToClipboard(copyValue);
if (isCopied) {
setCopied(value);
}
};
const handleSelectGroupBy = (key: string) => () => {
setGroupBy(key);
searchParams.set("groupBy", key);
setSearchParams(searchParams);
handleCloseOptions();
};
const handleToggleExpandAll = useCallback(() => {
setExpandGroups(new Array(groupData.length).fill(!expandAll));
}, [expandAll, groupData.length]);
@@ -105,11 +58,6 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
});
}, []);
useEffect(() => {
if (copied === null) return;
const timeout = setTimeout(() => setCopied(null), 2000);
return () => clearTimeout(timeout);
}, [copied]);
useEffect(() => {
setExpandGroups(new Array(groupData.length).fill(true));
@@ -124,38 +72,16 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
key={item.keysString}
>
<Accordion
key={String(expandGroups[i])}
defaultExpanded={expandGroups[i]}
onChange={handleChangeExpand(i)}
title={groupBy !== WITHOUT_GROUPING && (
<div className="vm-group-logs-section-keys">
<span className="vm-group-logs-section-keys__title">Group by <code>{groupBy}</code>:</span>
{item.pairs.map((pair) => (
<Tooltip
title={copied === pair ? "Copied" : "Copy to clipboard"}
key={`${item.keysString}_${pair}`}
placement={"top-center"}
>
<div
className={classNames({
"vm-group-logs-section-keys__pair": true,
"vm-group-logs-section-keys__pair_dark": isDarkTheme
})}
onClick={handleClickByPair(pair)}
>
{pair}
</div>
</Tooltip>
))}
<span className="vm-group-logs-section-keys__count">{item.values.length} entries</span>
</div>
)}
title={groupBy !== WITHOUT_GROUPING && <GroupLogsHeader group={item}/>}
>
<div className="vm-group-logs-section-rows">
{item.values.map((value) => (
<GroupLogsItem
key={`${value._msg}${value._time}`}
log={value}
displayFields={displayFields}
/>
))}
</div>
@@ -175,47 +101,7 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
ariaLabel={expandAll ? "Collapse All" : "Expand All"}
/>
</Tooltip>
<Tooltip title={"Group by"}>
<div ref={optionsButtonRef}>
<Button
variant="text"
startIcon={<StorageIcon/>}
onClick={toggleOpenOptions}
ariaLabel={"Group by"}
/>
</div>
</Tooltip>
{
<Popper
open={openOptions}
placement="bottom-right"
onClose={handleCloseOptions}
buttonRef={optionsButtonRef}
>
<div className="vm-list vm-group-logs-header-keys">
<div className="vm-group-logs-header-keys__search">
<TextField
label="Search key"
value={searchKey}
onChange={setSearchKey}
type="search"
/>
</div>
{filteredLogsKeys.map(id => (
<div
className={classNames({
"vm-list-item": true,
"vm-list-item_active": id === groupBy
})}
key={id}
onClick={handleSelectGroupBy(id)}
>
{id}
</div>
))}
</div>
</Popper>
}
<GroupLogsConfigurators logs={logs}/>
</div>
), settingsRef.current)}
</>

View File

@@ -1,8 +1,10 @@
import React, { FC, memo, useCallback, useEffect, useState } from "preact/compat";
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
import Button from "../../../components/Main/Button/Button";
import { CopyIcon } from "../../../components/Main/Icons";
import { CopyIcon, StorageIcon, VisibilityIcon } from "../../../components/Main/Icons";
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
import { useSearchParams } from "react-router-dom";
import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs";
interface Props {
field: string;
@@ -11,8 +13,17 @@ interface Props {
const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
const copyToClipboard = useCopyToClipboard();
const [searchParams, setSearchParams] = useSearchParams();
const [copied, setCopied] = useState<boolean>(false);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const displayFieldsString = searchParams.get(LOGS_URL_PARAMS.DISPLAY_FIELDS) || "";
const displayFields = displayFieldsString ? displayFieldsString.split(",") : [];
const isSelectedField = displayFields.includes(field);
const isGroupByField = groupBy === field;
const handleCopy = useCallback(async () => {
if (copied) return;
try {
@@ -23,6 +34,18 @@ const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
}
}, [copied, copyToClipboard]);
const handleSelectDisplayField = () => {
const prev = displayFields;
const newDisplayFields = prev.includes(field) ? prev.filter(v => v !== field) : [...prev, field];
searchParams.set(LOGS_URL_PARAMS.DISPLAY_FIELDS, newDisplayFields.join(","));
setSearchParams(searchParams);
};
const handleSelectGroupBy = () => {
isGroupByField ? searchParams.delete(LOGS_URL_PARAMS.GROUP_BY) : searchParams.set(LOGS_URL_PARAMS.GROUP_BY, field);
setSearchParams(searchParams);
};
useEffect(() => {
if (copied === null) return;
const timeout = setTimeout(() => setCopied(false), 2000);
@@ -35,6 +58,7 @@ const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
<div className="vm-group-logs-row-fields-item-controls__wrapper">
<Tooltip title={copied ? "Copied" : "Copy to clipboard"}>
<Button
className="vm-group-logs-row-fields-item-controls__button"
variant="text"
color="gray"
size="small"
@@ -43,6 +67,34 @@ const GroupLogsFieldRow: FC<Props> = ({ field, value }) => {
ariaLabel="copy to clipboard"
/>
</Tooltip>
<Tooltip
key={`${field}_${isSelectedField}_${isGroupByField}`}
title={isSelectedField ? "Hide this field" : "Show this field instead of the message"}
>
<Button
className="vm-group-logs-row-fields-item-controls__button"
variant="text"
color={isSelectedField ? "secondary" : "gray"}
size="small"
startIcon={isSelectedField ? <VisibilityIcon/> : <VisibilityIcon/>}
onClick={handleSelectDisplayField}
ariaLabel="copy to clipboard"
/>
</Tooltip>
<Tooltip
key={`${field}_${isSelectedField}_${isGroupByField}`}
title={isGroupByField ? "Ungroup this field" : "Group by this field"}
>
<Button
className="vm-group-logs-row-fields-item-controls__button"
variant="text"
color={isGroupByField ? "secondary" : "gray"}
size="small"
startIcon={<StorageIcon/>}
onClick={handleSelectGroupBy}
ariaLabel="copy to clipboard"
/>
</Tooltip>
</div>
</td>
<td className="vm-group-logs-row-fields-item__key">{field}</td>

View File

@@ -0,0 +1,127 @@
import React, { FC, useCallback, useEffect, useRef } from "preact/compat";
import classNames from "classnames";
import { useSearchParams } from "react-router-dom";
import { MouseEvent, useState } from "react";
import { useAppState } from "../../../state/common/StateContext";
import { Logs } from "../../../api/types";
import useEventListener from "../../../hooks/useEventListener";
import Popper from "../../../components/Main/Popper/Popper";
import useBoolean from "../../../hooks/useBoolean";
import GroupLogsHeaderItem from "./GroupLogsHeaderItem";
import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs";
interface Props {
group: {
keys: string[]
keysString: string
values: Logs[]
pairs: string[]
};
}
const GroupLogsHeader: FC<Props> = ({ group }) => {
const { isDarkTheme } = useAppState();
const [searchParams] = useSearchParams();
const containerRef = useRef<HTMLDivElement>(null);
const moreRef = useRef<HTMLDivElement>(null);
const {
value: openMore,
toggle: handleToggleMore,
setFalse: handleCloseMore,
} = useBoolean(false);
const [hideParisCount, setHideParisCount] = useState<number>(0);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const compactGroupHeader = searchParams.get(LOGS_URL_PARAMS.COMPACT_GROUP_HEADER) === "true";
const pairs = group.pairs;
const hideAboveIndex = pairs.length - hideParisCount - 1;
const handleClickMore = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
handleToggleMore();
};
const calcVisiblePairsCount = useCallback(() => {
if (!compactGroupHeader || !containerRef.current) {
setHideParisCount(0);
return;
}
const container = containerRef.current;
const containerSize = container.getBoundingClientRect();
const selector = ".vm-group-logs-section-keys__pair:not(.vm-group-logs-section-keys__pair_more)";
const children = Array.from(container.querySelectorAll(selector));
let count = 0;
for (const child of children) {
const { right } = (child as HTMLElement).getBoundingClientRect();
if ((right + 220) > containerSize.width) {
count++;
}
}
setHideParisCount(count);
}, [compactGroupHeader, containerRef]);
useEffect(calcVisiblePairsCount, [group.pairs, compactGroupHeader, containerRef]);
useEventListener("resize", calcVisiblePairsCount);
return (
<div
className={classNames({
"vm-group-logs-section-keys": true,
"vm-group-logs-section-keys_compact": compactGroupHeader,
})}
ref={containerRef}
>
<span className="vm-group-logs-section-keys__title">Group by <code>{groupBy}</code>:</span>
{pairs.map((pair, i) => (
<GroupLogsHeaderItem
key={`${group.keysString}_${pair}`}
pair={pair}
isHide={hideParisCount ? i > hideAboveIndex : false}
/>
))}
{hideParisCount > 0 && (
<>
<div
className={classNames({
"vm-group-logs-section-keys__pair": true,
"vm-group-logs-section-keys__pair_more": true,
"vm-group-logs-section-keys__pair_dark": isDarkTheme
})}
ref={moreRef}
onClick={handleClickMore}
>
+{hideParisCount} more
</div>
<Popper
open={openMore}
buttonRef={moreRef}
placement="bottom-left"
onClose={handleCloseMore}
>
<div className="vm-group-logs-section-keys vm-group-logs-section-keys_popper">
{pairs.slice(hideAboveIndex + 1).map((pair) => (
<GroupLogsHeaderItem
key={`${group.keysString}_${pair}`}
pair={pair}
/>
))}
</div>
</Popper>
</>
)}
<span className="vm-group-logs-section-keys__count">{group.values.length} entries</span>
</div>
)
;
};
export default GroupLogsHeader;

View File

@@ -0,0 +1,59 @@
import React, { FC, useEffect } from "preact/compat";
import { useAppState } from "../../../state/common/StateContext";
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
import classNames from "classnames";
import { MouseEvent, useState } from "react";
import useCopyToClipboard from "../../../hooks/useCopyToClipboard";
import { useSearchParams } from "react-router-dom";
import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs";
interface Props {
pair: string;
isHide?: boolean;
}
const GroupLogsHeaderItem: FC<Props> = ({ pair, isHide }) => {
const { isDarkTheme } = useAppState();
const copyToClipboard = useCopyToClipboard();
const [searchParams] = useSearchParams();
const [copied, setCopied] = useState<string | null>(null);
const groupBy = searchParams.get(LOGS_URL_PARAMS.GROUP_BY) || LOGS_GROUP_BY;
const handleClickByPair = (value: string) => async (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
const isKeyValue = /(.+)?=(".+")/.test(value);
const copyValue = isKeyValue ? `${value.replace(/=/, ": ")}` : `${groupBy}: "${value}"`;
const isCopied = await copyToClipboard(copyValue);
if (isCopied) {
setCopied(value);
}
};
useEffect(() => {
if (copied === null) return;
const timeout = setTimeout(() => setCopied(null), 2000);
return () => clearTimeout(timeout);
}, [copied]);
return (
<Tooltip
title={copied === pair ? "Copied" : "Copy to clipboard"}
placement={"top-center"}
>
<div
className={classNames({
"vm-group-logs-section-keys__pair": true,
"vm-group-logs-section-keys__pair_hide": isHide,
"vm-group-logs-section-keys__pair_dark": isDarkTheme
})}
onClick={handleClickByPair(pair)}
>
{pair}
</div>
</Tooltip>
);
};
export default GroupLogsHeaderItem;

View File

@@ -6,28 +6,34 @@ import { ArrowDownIcon } from "../../../components/Main/Icons";
import classNames from "classnames";
import { useLogsState } from "../../../state/logsPanel/LogsStateContext";
import dayjs from "dayjs";
import { DATE_TIME_FORMAT } from "../../../constants/date";
import { useTimeState } from "../../../state/time/TimeStateContext";
import GroupLogsFieldRow from "./GroupLogsFieldRow";
import { marked } from "marked";
import { useSearchParams } from "react-router-dom";
import { LOGS_DATE_FORMAT, LOGS_URL_PARAMS } from "../../../constants/logs";
interface Props {
log: Logs;
displayFields?: string[];
}
const GroupLogsItem: FC<Props> = ({ log }) => {
const GroupLogsItem: FC<Props> = ({ log, displayFields = ["_msg"] }) => {
const {
value: isOpenFields,
toggle: toggleOpenFields,
} = useBoolean(false);
const [searchParams] = useSearchParams();
const { markdownParsing } = useLogsState();
const { timezone } = useTimeState();
const noWrapLines = searchParams.get(LOGS_URL_PARAMS.NO_WRAP_LINES) === "true";
const dateFormat = searchParams.get(LOGS_URL_PARAMS.DATE_FORMAT) || LOGS_DATE_FORMAT;
const formattedTime = useMemo(() => {
if (!log._time) return "";
return dayjs(log._time).tz().format(`${DATE_TIME_FORMAT}.SSS`);
}, [log._time, timezone]);
return dayjs(log._time).tz().format(dateFormat);
}, [log._time, timezone, dateFormat]);
const formattedMarkdown = useMemo(() => {
if (!markdownParsing || !log._msg) return "";
@@ -38,6 +44,14 @@ const GroupLogsItem: FC<Props> = ({ log }) => {
const hasFields = fields.length > 0;
const displayMessage = useMemo(() => {
if (displayFields.length) {
return displayFields.filter(field => log[field]).map((field, i) => (
<span
className="vm-group-logs-row-content__sub-msg"
key={field + i}
>{log[field]}</span>
));
}
if (log._msg) return log._msg;
if (!hasFields) return;
const dataObject = fields.reduce<{ [key: string]: string }>((obj, [key, value]) => {
@@ -45,7 +59,7 @@ const GroupLogsItem: FC<Props> = ({ log }) => {
return obj;
}, {});
return JSON.stringify(dataObject);
}, [log, fields, hasFields]);
}, [log, fields, hasFields, displayFields]);
return (
<div className="vm-group-logs-row">
@@ -76,7 +90,8 @@ const GroupLogsItem: FC<Props> = ({ log }) => {
className={classNames({
"vm-group-logs-row-content__msg": true,
"vm-group-logs-row-content__msg_empty-msg": !log._msg,
"vm-group-logs-row-content__msg_missing": !displayMessage
"vm-group-logs-row-content__msg_missing": !displayMessage,
"vm-group-logs-row-content__msg_single-line": noWrapLines,
})}
dangerouslySetInnerHTML={(markdownParsing && formattedMarkdown) ? { __html: formattedMarkdown } : undefined}
>

View File

@@ -1,5 +1,7 @@
@use "src/styles/variables" as *;
$font-size-logs: var(--font-size-logs, $font-size-small);
.vm-group-logs {
margin-top: calc(-1 * $padding-medium);
@@ -19,22 +21,44 @@
}
&-section {
border-bottom: $border-divider;
&-keys {
position: relative;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: $padding-small;
border-bottom: $border-divider;
padding: $padding-small 0;
padding: $padding-small 120px $padding-small 0;
font-size: $font-size-logs;
&_compact {
flex-wrap: nowrap;
overflow: hidden;
}
&_popper {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding: $padding-global;
max-height: 400px;
overflow: auto;
}
&__title {
font-weight: bold;
white-space: nowrap;
code {
font-family: monospace;
&:before {
content: "\"";
}
&:after {
content: "\"";
}
@@ -42,19 +66,35 @@
}
&__count {
position: absolute;
top: auto;
right: 0;
flex-grow: 1;
text-align: right;
font-size: $font-size-small;
font-size: $font-size-logs;
color: $color-text-secondary;
padding-right: calc($padding-large * 3);
}
&__pair {
order: 0;
padding: calc($padding-global / 2) $padding-global;
background-color: lighten($color-tropical-blue, 6%);
color: darken($color-dodger-blue, 20%);
border-radius: $border-radius-medium;
transition: background-color 0.3s ease-in, transform 0.1s ease-in, opacity 0.3s ease-in;
white-space: nowrap;
&_hide {
order: 2;
visibility: hidden;
opacity: 0;
pointer-events: none;
}
&_more {
order: 1;
}
&:hover {
background-color: $color-tropical-blue;
@@ -84,13 +124,19 @@
&-row {
position: relative;
border-bottom: $border-divider;
&:last-child {
margin-bottom: $padding-small;
}
&-content {
position: relative;
display: grid;
grid-template-columns: auto minmax(180px, max-content) 1fr;
padding: $padding-global 0;
grid-template-columns: auto max-content 1fr;
padding: calc($padding-small / 4) 0;
font-size: $font-size-logs;
font-variant-numeric: tabular-nums;
line-height: 1.3;
cursor: pointer;
transition: background-color 0.2s ease-in;
@@ -116,8 +162,7 @@
display: flex;
align-items: flex-start;
justify-content: flex-end;
margin-right: $padding-small;
line-height: 1;
padding: 0 $padding-global 0 $padding-small;
white-space: nowrap;
&_missing {
@@ -130,7 +175,12 @@
&__msg {
font-family: $font-family-monospace;
overflow-wrap: anywhere;
line-height: 1.1;
&_single-line {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&_empty-msg {
overflow: hidden;
@@ -158,7 +208,7 @@
border-radius: $border-radius-small;
tab-size: 4;
font-variant-ligatures: none;
margin: calc($padding-small/4) 0;
margin: calc($padding-small / 4) 0;
}
p {
@@ -171,7 +221,7 @@
}
code {
font-size: $font-size-small;
font-size: $font-size-logs;
padding: calc($padding-small / 4) calc($padding-small / 2);
}
@@ -194,25 +244,35 @@
blockquote {
border-left: 4px solid $color-hover-black;
margin: calc($padding-small/2) $padding-small;
padding: calc($padding-small/2) $padding-small;
margin: calc($padding-small / 2) $padding-small;
padding: calc($padding-small / 2) $padding-small;
}
ul, ol {
list-style-position: inside;
}
/* end styles for markdown */
}
&__sub-msg {
padding-right: $padding-global;
}
}
&-fields {
position: relative;
grid-row: 2;
padding: $padding-small 0;
margin-bottom: $padding-small;
margin: $padding-small 0 $padding-small calc($padding-global * 2);
border: $border-divider;
border-radius: $border-radius-small;
overflow: auto;
max-height: 300px;
height: 300px;
resize: vertical;
font-family: $font-family-monospace;
font-size: $font-size-logs;
font-variant-numeric: tabular-nums;
&-item {
border-radius: $border-radius-small;
@@ -223,19 +283,26 @@
}
&-controls {
padding: 0;
padding: 0 calc($padding-small / 2);
&__wrapper {
display: flex;
align-items: center;
justify-content: center;
}
&__button.vm-button_small {
width: 22px;
height: 22px;
min-height: 22px;
}
}
&__key,
&__value {
vertical-align: top;
padding: calc($padding-small / 2) $padding-global;
line-height: $font-size;
padding: calc($padding-small / 2);
}
&__key {

View File

@@ -1,15 +1,20 @@
import { getFromStorage, saveToStorage } from "../../utils/storage";
import { LogsFiledValues } from "../../api/types";
import { AUTOCOMPLETE_LIMITS } from "../../constants/queryAutocomplete";
export interface LogsState {
markdownParsing: boolean;
autocompleteCache: Map<string, LogsFiledValues[]>;
}
export type LogsAction =
| { type: "SET_MARKDOWN_PARSING", payload: boolean }
| { type: "SET_AUTOCOMPLETE_CACHE", payload: { key: string, value: LogsFiledValues[] } }
export const initialLogsState: LogsState = {
markdownParsing: getFromStorage("LOGS_MARKDOWN") === "true",
autocompleteCache: new Map<string, LogsFiledValues[]>(),
};
export function reducer(state: LogsState, action: LogsAction): LogsState {
@@ -20,6 +25,18 @@ export function reducer(state: LogsState, action: LogsAction): LogsState {
...state,
markdownParsing: action.payload
};
case "SET_AUTOCOMPLETE_CACHE": {
if (state.autocompleteCache.size >= AUTOCOMPLETE_LIMITS.cacheLimit) {
const firstKey = state.autocompleteCache.keys().next().value;
state.autocompleteCache.delete(firstKey);
}
state.autocompleteCache.set(action.payload.key, action.payload.value);
return {
...state,
autocompleteCache: state.autocompleteCache,
};
}
default:
throw new Error();
}

View File

@@ -10,7 +10,7 @@ import (
// specific files
// static content
//
//go:embed favicon-32x32.png robots.txt index.html manifest.json asset-manifest.json
//go:embed favicon.svg robots.txt index.html manifest.json asset-manifest.json
//go:embed static
var files embed.FS

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"slices"
"sort"
"strconv"
"strings"
"testing"
@@ -103,6 +104,25 @@ func NewPrometheusAPIV1QueryResponse(t *testing.T, s string) *PrometheusAPIV1Que
return res
}
// Sort performs data.Result sort by metric labels
func (pqr *PrometheusAPIV1QueryResponse) Sort() {
sort.Slice(pqr.Data.Result, func(i, j int) bool {
leftS := make([]string, 0, len(pqr.Data.Result[i].Metric))
rightS := make([]string, 0, len(pqr.Data.Result[j].Metric))
for k, v := range pqr.Data.Result[i].Metric {
leftS = append(leftS, fmt.Sprintf("%s=%s", k, v))
}
for k, v := range pqr.Data.Result[j].Metric {
rightS = append(rightS, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(leftS)
sort.Strings(rightS)
return strings.Join(leftS, ",") < strings.Join(rightS, ",")
})
}
// QueryData holds the query result along with its type.
type QueryData struct {
ResultType string

View File

@@ -0,0 +1,318 @@
package tests
import (
"os"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
at "github.com/VictoriaMetrics/VictoriaMetrics/apptest"
pb "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
)
func TestSingleIngestionProtocols(t *testing.T) {
os.RemoveAll(t.Name())
tc := at.NewTestCase(t)
defer tc.Stop()
sut := tc.MustStartDefaultVmsingle()
type opts struct {
query string
wantMetrics []map[string]string
wantSamples []*at.Sample
}
f := func(sut at.PrometheusQuerier, opts *opts) {
t.Helper()
wantResult := []*at.QueryResult{}
for idx, wm := range opts.wantMetrics {
wantResult = append(wantResult, &at.QueryResult{
Metric: wm,
Samples: []*at.Sample{opts.wantSamples[idx]},
})
}
tc.Assert(&at.AssertOptions{
Msg: "unexpected /export query response",
Got: func() any {
got := sut.PrometheusAPIV1Export(t, opts.query, at.QueryOpts{
Start: "2024-02-05T08:50:00.700Z",
End: "2024-02-05T09:00:00.700Z",
})
got.Sort()
return got
},
Want: &at.PrometheusAPIV1QueryResponse{Data: &at.QueryData{Result: wantResult}},
CmpOpts: []cmp.Option{
cmpopts.IgnoreFields(at.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType"),
},
})
}
// influx line format
sut.InfluxWrite(t, []string{
`influxline series1=10 1707123456700`, // 2024-02-05T08:57:36.700Z
`influxline,label=foo1,label1=value1,label2=value2 series2=40 1707123456800`, // 2024-02-05T08:57:36.800Z
}, at.QueryOpts{})
sut.ForceFlush(t)
f(sut, &opts{
query: `{__name__=~"influxline.+"}`,
wantMetrics: []map[string]string{
{
"__name__": "influxline_series1",
},
{
"__name__": "influxline_series2",
"label": "foo1",
"label1": "value1",
"label2": "value2",
},
},
wantSamples: []*at.Sample{
{Timestamp: 1707123456700, Value: 10},
{Timestamp: 1707123456800, Value: 40},
},
})
// prometheus text exposition format
sut.PrometheusAPIV1ImportPrometheus(t, []string{
`importprometheus_series 10 1707123456700`, // 2024-02-05T08:57:36.700Z
`importprometheus_series2{label="foo",label1="value1"} 20 1707123456800`, // 2024-02-05T08:57:36.800Z
}, at.QueryOpts{})
sut.ForceFlush(t)
f(sut, &opts{
query: `{__name__=~"importprometheus.+"}`,
wantMetrics: []map[string]string{
{
"__name__": "importprometheus_series",
},
{
"__name__": "importprometheus_series2",
"label": "foo",
"label1": "value1",
},
},
wantSamples: []*at.Sample{
{Timestamp: 1707123456700, Value: 10},
{Timestamp: 1707123456800, Value: 20},
},
})
// prometheus remote write format
pbData := []pb.TimeSeries{
{
Labels: []pb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
},
Samples: []pb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
},
},
{
Labels: []pb.Label{
{
Name: "__name__",
Value: "prometheusrw_series2",
},
{
Name: "label",
Value: "foo2",
},
{
Name: "label1",
Value: "value1",
},
},
Samples: []pb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
},
},
}
sut.PrometheusAPIV1Write(t, pbData, at.QueryOpts{})
sut.ForceFlush(t)
f(sut, &opts{
query: `{__name__=~"prometheusrw.+"}`,
wantMetrics: []map[string]string{
{
"__name__": "prometheusrw_series",
},
{
"__name__": "prometheusrw_series2",
"label": "foo2",
"label1": "value1",
},
},
wantSamples: []*at.Sample{
{Timestamp: 1707123456700, Value: 10}, // 2024-02-05T08:57:36.700Z
{Timestamp: 1707123456800, Value: 20}, // 2024-02-05T08:57:36.700Z
},
})
}
func TestClusterIngestionProtocols(t *testing.T) {
os.RemoveAll(t.Name())
tc := at.NewTestCase(t)
defer tc.Stop()
vmstorage := tc.MustStartVmstorage("vmstorage", []string{
"-storageDataPath=" + tc.Dir() + "/vmstorage",
"-retentionPeriod=100y",
})
vminsert := tc.MustStartVminsert("vminsert", []string{
"-storageNode=" + vmstorage.VminsertAddr(),
})
vmselect := tc.MustStartVmselect("vmselect", []string{
"-storageNode=" + vmstorage.VmselectAddr(),
})
type opts struct {
query string
wantMetrics []map[string]string
wantSamples []*at.Sample
}
f := func(opts *opts) {
t.Helper()
wantResult := []*at.QueryResult{}
for idx, wm := range opts.wantMetrics {
wantResult = append(wantResult, &at.QueryResult{
Metric: wm,
Samples: []*at.Sample{opts.wantSamples[idx]},
})
}
tc.Assert(&at.AssertOptions{
Msg: "unexpected /export query response",
Got: func() any {
got := vmselect.PrometheusAPIV1Export(t, opts.query, at.QueryOpts{
Start: "2024-02-05T08:50:00.700Z",
End: "2024-02-05T09:00:00.700Z",
})
got.Sort()
return got
},
Want: &at.PrometheusAPIV1QueryResponse{Data: &at.QueryData{Result: wantResult}},
CmpOpts: []cmp.Option{
cmpopts.IgnoreFields(at.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType"),
},
})
}
// prometheus text exposition format
vminsert.PrometheusAPIV1ImportPrometheus(t, []string{
`importprometheus_series 10 1707123456700`, // 2024-02-05T08:57:36.700Z
`importprometheus_series2{label="foo",label1="value1"} 20 1707123456800`, // 2024-02-05T08:57:36.800Z
}, at.QueryOpts{})
vmstorage.ForceFlush(t)
f(&opts{
query: `{__name__=~"importprometheus.+"}`,
wantMetrics: []map[string]string{
{
"__name__": "importprometheus_series",
},
{
"__name__": "importprometheus_series2",
"label": "foo",
"label1": "value1",
},
},
wantSamples: []*at.Sample{
{Timestamp: 1707123456700, Value: 10},
{Timestamp: 1707123456800, Value: 20},
},
})
// influx line format
vminsert.InfluxWrite(t, []string{
`influxline series1=10 1707123456700`, // 2024-02-05T08:57:36.700Z
`influxline,label=foo1,label1=value1,label2=value2 series2=40 1707123456800`, // 2024-02-05T08:57:36.800Z
}, at.QueryOpts{})
vmstorage.ForceFlush(t)
f(&opts{
query: `{__name__=~"influxline.+"}`,
wantMetrics: []map[string]string{
{
"__name__": "influxline_series1",
},
{
"__name__": "influxline_series2",
"label": "foo1",
"label1": "value1",
"label2": "value2",
},
},
wantSamples: []*at.Sample{
{Timestamp: 1707123456700, Value: 10},
{Timestamp: 1707123456800, Value: 40},
},
})
// prometheus remote write format
pbData := []pb.TimeSeries{
{
Labels: []pb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
},
Samples: []pb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
},
},
{
Labels: []pb.Label{
{
Name: "__name__",
Value: "prometheusrw_series2",
},
{
Name: "label",
Value: "foo2",
},
{
Name: "label1",
Value: "value1",
},
},
Samples: []pb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
},
},
}
vminsert.PrometheusAPIV1Write(t, pbData, at.QueryOpts{})
vmstorage.ForceFlush(t)
f(&opts{
query: `{__name__=~"prometheusrw.+"}`,
wantMetrics: []map[string]string{
{
"__name__": "prometheusrw_series",
},
{
"__name__": "prometheusrw_series2",
"label": "foo2",
"label1": "value1",
},
},
wantSamples: []*at.Sample{
{Timestamp: 1707123456700, Value: 10}, // 2024-02-05T08:57:36.700Z
{Timestamp: 1707123456800, Value: 20}, // 2024-02-05T08:57:36.700Z
},
})
}

View File

@@ -50,7 +50,7 @@ func TestClusterKeyConceptsQueryData(t *testing.T) {
testKeyConceptsQueryData(t, sut)
}
// testClusterKeyConceptsQuery verifies cases from https://docs.victoriametrics.com/keyconcepts/#query-data
// testKeyConceptsQueryData verifies cases from https://docs.victoriametrics.com/keyconcepts/#query-data
func testKeyConceptsQueryData(t *testing.T, sut at.PrometheusWriteQuerier) {
// Insert example data from documentation.

View File

@@ -78,6 +78,19 @@ func (app *Vminsert) ClusternativeListenAddr() string {
return app.clusternativeListenAddr
}
// InfluxWrite is a test helper function that inserts a
// collection of records in Influx line format by sending a HTTP
// POST request to /influx/write vmsingle endpoint.
//
// See https://docs.victoriametrics.com/url-examples/#influxwrite
func (app *Vminsert) InfluxWrite(t *testing.T, records []string, opts QueryOpts) {
t.Helper()
url := fmt.Sprintf("http://%s/insert/%s/influx/write", app.httpListenAddr, opts.getTenant())
data := []byte(strings.Join(records, "\n"))
app.cli.Post(t, url, "text/plain", data, http.StatusNoContent)
}
// PrometheusAPIV1Write is a test helper function that inserts a
// collection of records in Prometheus remote-write format by sending a HTTP
// POST request to /prometheus/api/v1/write vminsert endpoint.

View File

@@ -1,12 +1,7 @@
FROM node:lts-alpine3.20
ENV PATH="/home/node/node_modules/.bin:$PATH"
WORKDIR /home/node
COPY package.json .
COPY package-lock.json .
RUN npm ci
WORKDIR /victoriametrics
WORKDIR /opt/node
COPY yarn.lock package.json .
RUN yarn install
WORKDIR /vm
ENTRYPOINT ["/opt/node/node_modules/.bin/cspell"]

View File

@@ -2,27 +2,27 @@
# Builds cspell image.
cspell-install:
@ (docker inspect cspell > /dev/null) || (docker build cspell --tag cspell)
@docker build cspell -t cspell
# Checks for spelling errors.
cspell-check: cspell-install
@CMD="cspell --no-progress" $(MAKE) cspell-run-command
cspell-check: CMD="--no-progress -r /vm"
cspell-check: cspell-install cspell-run
# Runs spelling error check.
# A user facing alias to cspell-check command.
spellcheck: cspell-check
# Runs cspell container commands.
cspell-run-command:
@cp cspell/cspell.json cspell.json
cspell-run:
@-docker run \
--entrypoint /bin/sh \
--mount type=bind,src=".",dst=/victoriametrics \
--mount type=bind,src="$(PWD)",dst=/vm \
--rm \
--tty \
cspell -c "$(CMD)"
@rm cspell.json
cspell -c cspell/cspell.json "$(CMD)"
cspell-update-deps: cspell-install
@CMD="cd /victoriametrics/cspell && npm update && rm -rf ./node_modules" $(MAKE) cspell-run-command
@-docker run \
--mount type=bind,src="$(PWD)",dst=/vm \
--entrypoint=/bin/sh \
--workdir=/vm/cspell \
--rm \
cspell -c "yarn install && yarn upgrade && rm -rf ./node_modules"

View File

@@ -7,7 +7,7 @@
{
"addWords": true,
"name": "custom-dict",
"path": "cspell/custom-dict.txt"
"path": "custom-dict.txt"
}
],
"dictionaries": [
@@ -50,18 +50,18 @@
"/vm[a-zA-Z0-9-_]+/i"
],
"import": [
"/home/node/node_modules/@cspell/dict-aws/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-companies/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-data-science/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-en_us/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-fullstack/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-golang/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-k8s/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-people-names/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-ru_ru/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-software-terms/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-uk-ua/cspell-ext.json",
"/home/node/node_modules/@cspell/dict-win32/cspell-ext.json"
"@cspell/dict-aws/cspell-ext.json",
"@cspell/dict-companies/cspell-ext.json",
"@cspell/dict-data-science/cspell-ext.json",
"@cspell/dict-en_us/cspell-ext.json",
"@cspell/dict-fullstack/cspell-ext.json",
"@cspell/dict-golang/cspell-ext.json",
"@cspell/dict-k8s/cspell-ext.json",
"@cspell/dict-people-names/cspell-ext.json",
"@cspell/dict-ru_ru/cspell-ext.json",
"@cspell/dict-software-terms/cspell-ext.json",
"@cspell/dict-uk-ua/cspell-ext.json",
"@cspell/dict-win32/cspell-ext.json"
],
"useGitignore": true
}

View File

@@ -145,3 +145,5 @@ SSZ
DDZ
DOKS
iforest
deltatocumulative
TLSCA

1445
cspell/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,6 @@
"@cspell/dict-software-terms": "^4.1.3",
"@cspell/dict-uk-ua": "^4.0.1",
"@cspell/dict-win32": "^2.0.3",
"cspell": "^8.14.2"
"cspell": "^8.17.1"
}
}

793
cspell/yarn.lock Normal file
View File

@@ -0,0 +1,793 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@cspell/cspell-bundled-dicts@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.17.1.tgz#61adad73f1bb1e12b182ffa04423d6052b18f0fc"
integrity sha512-HmkXS5uX4bk/XxsRS4Q+zRvhgRa81ddGiR2/Xfag9MIi5L5UnEJ4g21EpmIlXkMxYrTu2fp69SZFss5NfcFF9Q==
dependencies:
"@cspell/dict-ada" "^4.0.5"
"@cspell/dict-al" "^1.0.3"
"@cspell/dict-aws" "^4.0.7"
"@cspell/dict-bash" "^4.1.8"
"@cspell/dict-companies" "^3.1.8"
"@cspell/dict-cpp" "^6.0.2"
"@cspell/dict-cryptocurrencies" "^5.0.3"
"@cspell/dict-csharp" "^4.0.5"
"@cspell/dict-css" "^4.0.16"
"@cspell/dict-dart" "^2.2.4"
"@cspell/dict-django" "^4.1.3"
"@cspell/dict-docker" "^1.1.11"
"@cspell/dict-dotnet" "^5.0.8"
"@cspell/dict-elixir" "^4.0.6"
"@cspell/dict-en-common-misspellings" "^2.0.7"
"@cspell/dict-en-gb" "1.1.33"
"@cspell/dict-en_us" "^4.3.28"
"@cspell/dict-filetypes" "^3.0.9"
"@cspell/dict-flutter" "^1.0.3"
"@cspell/dict-fonts" "^4.0.3"
"@cspell/dict-fsharp" "^1.0.4"
"@cspell/dict-fullstack" "^3.2.3"
"@cspell/dict-gaming-terms" "^1.0.9"
"@cspell/dict-git" "^3.0.3"
"@cspell/dict-golang" "^6.0.17"
"@cspell/dict-google" "^1.0.4"
"@cspell/dict-haskell" "^4.0.4"
"@cspell/dict-html" "^4.0.10"
"@cspell/dict-html-symbol-entities" "^4.0.3"
"@cspell/dict-java" "^5.0.10"
"@cspell/dict-julia" "^1.0.4"
"@cspell/dict-k8s" "^1.0.9"
"@cspell/dict-latex" "^4.0.3"
"@cspell/dict-lorem-ipsum" "^4.0.3"
"@cspell/dict-lua" "^4.0.6"
"@cspell/dict-makefile" "^1.0.3"
"@cspell/dict-markdown" "^2.0.7"
"@cspell/dict-monkeyc" "^1.0.9"
"@cspell/dict-node" "^5.0.5"
"@cspell/dict-npm" "^5.1.17"
"@cspell/dict-php" "^4.0.13"
"@cspell/dict-powershell" "^5.0.13"
"@cspell/dict-public-licenses" "^2.0.11"
"@cspell/dict-python" "^4.2.13"
"@cspell/dict-r" "^2.0.4"
"@cspell/dict-ruby" "^5.0.7"
"@cspell/dict-rust" "^4.0.10"
"@cspell/dict-scala" "^5.0.6"
"@cspell/dict-software-terms" "^4.1.19"
"@cspell/dict-sql" "^2.1.8"
"@cspell/dict-svelte" "^1.0.5"
"@cspell/dict-swift" "^2.0.4"
"@cspell/dict-terraform" "^1.0.6"
"@cspell/dict-typescript" "^3.1.11"
"@cspell/dict-vue" "^3.0.3"
"@cspell/cspell-json-reporter@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.17.1.tgz#c1678665f183589e5fc19a1c0933b8d362165a43"
integrity sha512-EV9Xkh42Xw3aORvDZfxusICX91DDbqQpYdGKBdPGuhgxWOUYYZKpLXsHCmDkhruMPo2m5gDh++/OqjLRPZofKQ==
dependencies:
"@cspell/cspell-types" "8.17.1"
"@cspell/cspell-pipe@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/cspell-pipe/-/cspell-pipe-8.17.1.tgz#c247d4bd1c8ec43c49c46dc4458f00489e98232b"
integrity sha512-uhC99Ox+OH3COSgShv4fpVHiotR70dNvAOSkzRvKVRzV6IGyFnxHjmyVVPEV0dsqzVLxltwYTqFhwI+UOwm45A==
"@cspell/cspell-resolver@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/cspell-resolver/-/cspell-resolver-8.17.1.tgz#6377c9c8c05c940fee675c74e31f893b7b2f38ab"
integrity sha512-XEK2ymTdQNgsV3ny60VkKzWskbICl4zNXh/DbxsoRXHqIRg43MXFpTNkEJ7j873EqdX7BU4opQQ+5D4stWWuhQ==
dependencies:
global-directory "^4.0.1"
"@cspell/cspell-service-bus@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/cspell-service-bus/-/cspell-service-bus-8.17.1.tgz#8d6d82ea3ab0fc9d7efed8523b070e4842780bd1"
integrity sha512-2sFWQtMEWZ4tdz7bw0bAx4NaV1t0ynGfjpuKWdQppsJFKNb+ZPZZ6Ah1dC13AdRRMZaG194kDRFwzNvRaCgWkQ==
"@cspell/cspell-types@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/cspell-types/-/cspell-types-8.17.1.tgz#5512030b4c2e7881a8822ab3afabbd4f5ddffb6f"
integrity sha512-NJbov7Jp57fh8addoxesjb8atg/APQfssCH5Q9uZuHBN06wEJDgs7fhfE48bU+RBViC9gltblsYZzZZQKzHYKg==
"@cspell/dict-ada@^4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@cspell/dict-ada/-/dict-ada-4.0.5.tgz#c14aae2faaecbad2d99f0d701e4700a48c68ef60"
integrity sha512-6/RtZ/a+lhFVmrx/B7bfP7rzC4yjEYe8o74EybXcvu4Oue6J4Ey2WSYj96iuodloj1LWrkNCQyX5h4Pmcj0Iag==
"@cspell/dict-al@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-al/-/dict-al-1.0.3.tgz#09e288b5ab56b126dce895d3301faf7c0dd732d6"
integrity sha512-V1HClwlfU/qwSq2Kt+MkqRAsonNu3mxjSCDyGRecdLGIHmh7yeEeaxqRiO/VZ4KP+eVSiSIlbwrb5YNFfxYZbw==
"@cspell/dict-aws@^4.0.4", "@cspell/dict-aws@^4.0.7":
version "4.0.7"
resolved "https://registry.yarnpkg.com/@cspell/dict-aws/-/dict-aws-4.0.7.tgz#f96f3b70cd52a25b895eb08e297de5a5cc3fc5b6"
integrity sha512-PoaPpa2NXtSkhGIMIKhsJUXB6UbtTt6Ao3x9JdU9kn7fRZkwD4RjHDGqulucIOz7KeEX/dNRafap6oK9xHe4RA==
"@cspell/dict-bash@^4.1.8":
version "4.1.8"
resolved "https://registry.yarnpkg.com/@cspell/dict-bash/-/dict-bash-4.1.8.tgz#26dc898e06eddea069cf1ad475ee0e867c89e632"
integrity sha512-I2CM2pTNthQwW069lKcrVxchJGMVQBzru2ygsHCwgidXRnJL/NTjAPOFTxN58Jc1bf7THWghfEDyKX/oyfc0yg==
"@cspell/dict-companies@^3.1.8":
version "3.1.10"
resolved "https://registry.yarnpkg.com/@cspell/dict-companies/-/dict-companies-3.1.10.tgz#1351959ba14a2c1f0678dd99069240748a724aa2"
integrity sha512-KpRLiVDCpTkF+IjWnuYc31B0gyHVh0TSf/MDrWPobl9oYNQRWFUMACAJO9FP+kHI0jzLjTyLC1KpKwqte/88iA==
"@cspell/dict-cpp@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@cspell/dict-cpp/-/dict-cpp-6.0.2.tgz#e4549ee1bdf4b6402c0b978eb9dd3deac0eb05df"
integrity sha512-yw5eejWvY4bAnc6LUA44m4WsFwlmgPt2uMSnO7QViGMBDuoeopMma4z9XYvs4lSjTi8fIJs/A1YDfM9AVzb8eg==
"@cspell/dict-cryptocurrencies@^5.0.3":
version "5.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.3.tgz#502f9fffcb2835a3379668ddebdc487678ce6207"
integrity sha512-bl5q+Mk+T3xOZ12+FG37dB30GDxStza49Rmoax95n37MTLksk9wBo1ICOlPJ6PnDUSyeuv4SIVKgRKMKkJJglA==
"@cspell/dict-csharp@^4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@cspell/dict-csharp/-/dict-csharp-4.0.5.tgz#c677c50be09ca5bb3a2cc0be15f3cd05141fd2f7"
integrity sha512-c/sFnNgtRwRJxtC3JHKkyOm+U3/sUrltFeNwml9VsxKBHVmvlg4tk4ar58PdpW9/zTlGUkWi2i85//DN1EsUCA==
"@cspell/dict-css@^4.0.16":
version "4.0.16"
resolved "https://registry.yarnpkg.com/@cspell/dict-css/-/dict-css-4.0.16.tgz#b7b87b5ea0f1157b023205bdb00070a7d231e367"
integrity sha512-70qu7L9z/JR6QLyJPk38fNTKitlIHnfunx0wjpWQUQ8/jGADIhMCrz6hInBjqPNdtGpYm8d1dNFyF8taEkOgrQ==
"@cspell/dict-dart@^2.2.4":
version "2.2.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-dart/-/dict-dart-2.2.4.tgz#8b877161ccdc65cead912b742b71aa55099c1706"
integrity sha512-of/cVuUIZZK/+iqefGln8G3bVpfyN6ZtH+LyLkHMoR5tEj+2vtilGNk9ngwyR8L4lEqbKuzSkOxgfVjsXf5PsQ==
"@cspell/dict-data-science@^2.0.1", "@cspell/dict-data-science@^2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@cspell/dict-data-science/-/dict-data-science-2.0.5.tgz#816e9b394c2a423d14cdc9a5de5d6fc6141d3900"
integrity sha512-nNSILXmhSJox9/QoXICPQgm8q5PbiSQP4afpbkBqPi/u/b3K9MbNH5HvOOa6230gxcGdbZ9Argl2hY/U8siBlg==
"@cspell/dict-django@^4.1.3":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-django/-/dict-django-4.1.3.tgz#a02a4a9ef8c9f47344f2d4a0c3964bcb62069ef5"
integrity sha512-yBspeL3roJlO0a1vKKNaWABURuHdHZ9b1L8d3AukX0AsBy9snSggc8xCavPmSzNfeMDXbH+1lgQiYBd3IW03fg==
"@cspell/dict-docker@^1.1.11":
version "1.1.11"
resolved "https://registry.yarnpkg.com/@cspell/dict-docker/-/dict-docker-1.1.11.tgz#6fce86eb6d86d73f77e18d3e7b9747bad3ca98de"
integrity sha512-s0Yhb16/R+UT1y727ekbR/itWQF3Qz275DR1ahOa66wYtPjHUXmhM3B/LT3aPaX+hD6AWmK23v57SuyfYHUjsw==
"@cspell/dict-dotnet@^5.0.8":
version "5.0.8"
resolved "https://registry.yarnpkg.com/@cspell/dict-dotnet/-/dict-dotnet-5.0.8.tgz#8a110ca302946025e0273a9940079483ec33a88a"
integrity sha512-MD8CmMgMEdJAIPl2Py3iqrx3B708MbCIXAuOeZ0Mzzb8YmLmiisY7QEYSZPg08D7xuwARycP0Ki+bb0GAkFSqg==
"@cspell/dict-elixir@^4.0.6":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@cspell/dict-elixir/-/dict-elixir-4.0.6.tgz#3d8965c558d8afd190356e9a900b02c546741feb"
integrity sha512-TfqSTxMHZ2jhiqnXlVKM0bUADtCvwKQv2XZL/DI0rx3doG8mEMS8SGPOmiyyGkHpR/pGOq18AFH3BEm4lViHIw==
"@cspell/dict-en-common-misspellings@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.0.7.tgz#62861cc9e813c947ebd71c7a50fc720767b4b543"
integrity sha512-qNFo3G4wyabcwnM+hDrMYKN9vNVg/k9QkhqSlSst6pULjdvPyPs1mqz1689xO/v9t8e6sR4IKc3CgUXDMTYOpA==
"@cspell/dict-en-gb@1.1.33":
version "1.1.33"
resolved "https://registry.yarnpkg.com/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz#7f1fd90fc364a5cb77111b5438fc9fcf9cc6da0e"
integrity sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==
"@cspell/dict-en_us@^4.3.23", "@cspell/dict-en_us@^4.3.28":
version "4.3.28"
resolved "https://registry.yarnpkg.com/@cspell/dict-en_us/-/dict-en_us-4.3.28.tgz#41169e1ed18465e7ff367a4f4488d4cbc6cf0baa"
integrity sha512-BN1PME7cOl7DXRQJ92pEd1f0Xk5sqjcDfThDGkKcsgwbSOY7KnTc/czBW6Pr3WXIchIm6cT12KEfjNqx7U7Rrw==
"@cspell/dict-filetypes@^3.0.9":
version "3.0.9"
resolved "https://registry.yarnpkg.com/@cspell/dict-filetypes/-/dict-filetypes-3.0.9.tgz#f4d5c35c341e6c3b77c08aec00678412641e1504"
integrity sha512-U7ycC1cE32A5aEgwzp/iE0TVabonUFnVt+Ygbf6NsIWqEuFWZgZChC7gfztA4T1fpuj602nFdp7eOnTWKORsnQ==
"@cspell/dict-flutter@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-flutter/-/dict-flutter-1.0.3.tgz#23e552209ab2238733d30ca3f2a141359756af51"
integrity sha512-52C9aUEU22ptpgYh6gQyIdA4MP6NPwzbEqndfgPh3Sra191/kgs7CVqXiO1qbtZa9gnYHUoVApkoxRE7mrXHfg==
"@cspell/dict-fonts@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-fonts/-/dict-fonts-4.0.3.tgz#abf578c10a2e7b2bd8f4374002677625288560d9"
integrity sha512-sPd17kV5qgYXLteuHFPn5mbp/oCHKgitNfsZLFC3W2fWEgZlhg4hK+UGig3KzrYhhvQ8wBnmZrAQm0TFKCKzsA==
"@cspell/dict-fsharp@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-fsharp/-/dict-fsharp-1.0.4.tgz#19a7263a61ca89cd3ec9c17537e424907b81ef38"
integrity sha512-G5wk0o1qyHUNi9nVgdE1h5wl5ylq7pcBjX8vhjHcO4XBq20D5eMoXjwqMo/+szKAqzJ+WV3BgAL50akLKrT9Rw==
"@cspell/dict-fullstack@^3.2.0", "@cspell/dict-fullstack@^3.2.3":
version "3.2.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-fullstack/-/dict-fullstack-3.2.3.tgz#f6fff74eff00c6759cba510168acada0619004cc"
integrity sha512-62PbndIyQPH11mAv0PyiyT0vbwD0AXEocPpHlCHzfb5v9SspzCCbzQ/LIBiFmyRa+q5LMW35CnSVu6OXdT+LKg==
"@cspell/dict-gaming-terms@^1.0.9":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.0.9.tgz#6b920386d281b89f70857e6dacea10ab89e88658"
integrity sha512-AVIrZt3YiUnxsUzzGYTZ1XqgtkgwGEO0LWIlEf+SiDUEVLtv4CYmmyXFQ+WXDN0pyJ0wOwDazWrP0Cu7avYQmQ==
"@cspell/dict-git@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-git/-/dict-git-3.0.3.tgz#3a3805ab9902bffc9255ec48f648145b957eb30b"
integrity sha512-LSxB+psZ0qoj83GkyjeEH/ZViyVsGEF/A6BAo8Nqc0w0HjD2qX/QR4sfA6JHUgQ3Yi/ccxdK7xNIo67L2ScW5A==
"@cspell/dict-golang@^6.0.12", "@cspell/dict-golang@^6.0.17":
version "6.0.17"
resolved "https://registry.yarnpkg.com/@cspell/dict-golang/-/dict-golang-6.0.17.tgz#8f3c11189b869db7216cb4496514b9882d1e30a5"
integrity sha512-uDDLEJ/cHdLiqPw4+5BnmIo2i/TSR+uDvYd6JlBjTmjBKpOCyvUgYRztH7nv5e7virsN5WDiUWah4/ATQGz4Pw==
"@cspell/dict-google@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-google/-/dict-google-1.0.4.tgz#e15a7ea2dee73800231a81840a59d3b50d49346f"
integrity sha512-JThUT9eiguCja1mHHLwYESgxkhk17Gv7P3b1S7ZJzXw86QyVHPrbpVoMpozHk0C9o+Ym764B7gZGKmw9uMGduQ==
"@cspell/dict-haskell@^4.0.4":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-haskell/-/dict-haskell-4.0.4.tgz#37e9cb9a7f5be337a697bcffd0a0d25e80aab50d"
integrity sha512-EwQsedEEnND/vY6tqRfg9y7tsnZdxNqOxLXSXTsFA6JRhUlr8Qs88iUUAfsUzWc4nNmmzQH2UbtT25ooG9x4nA==
"@cspell/dict-html-symbol-entities@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.3.tgz#bf2887020ca4774413d8b1f27c9b6824ba89e9ef"
integrity sha512-aABXX7dMLNFdSE8aY844X4+hvfK7977sOWgZXo4MTGAmOzR8524fjbJPswIBK7GaD3+SgFZ2yP2o0CFvXDGF+A==
"@cspell/dict-html@^4.0.10":
version "4.0.10"
resolved "https://registry.yarnpkg.com/@cspell/dict-html/-/dict-html-4.0.10.tgz#7b536b2adca4b58ed92752c9d3c7ffc724dd5991"
integrity sha512-I9uRAcdtHbh0wEtYZlgF0TTcgH0xaw1B54G2CW+tx4vHUwlde/+JBOfIzird4+WcMv4smZOfw+qHf7puFUbI5g==
"@cspell/dict-java@^5.0.10":
version "5.0.10"
resolved "https://registry.yarnpkg.com/@cspell/dict-java/-/dict-java-5.0.10.tgz#e6383ca645046b9f05a04a2c2e858fcc80c6fc63"
integrity sha512-pVNcOnmoGiNL8GSVq4WbX/Vs2FGS0Nej+1aEeGuUY9CU14X8yAVCG+oih5ZoLt1jaR8YfR8byUF8wdp4qG4XIw==
"@cspell/dict-julia@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-julia/-/dict-julia-1.0.4.tgz#e478c20d742cd6857b6de41dc61a92036dafb4bc"
integrity sha512-bFVgNX35MD3kZRbXbJVzdnN7OuEqmQXGpdOi9jzB40TSgBTlJWA4nxeAKV4CPCZxNRUGnLH0p05T/AD7Aom9/w==
"@cspell/dict-k8s@^1.0.6", "@cspell/dict-k8s@^1.0.9":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@cspell/dict-k8s/-/dict-k8s-1.0.9.tgz#e9392a002797c67ffc3e96893156cc15af3774d1"
integrity sha512-Q7GELSQIzo+BERl2ya/nBEnZeQC+zJP19SN1pI6gqDYraM51uYJacbbcWLYYO2Y+5joDjNt/sd/lJtLaQwoSlA==
"@cspell/dict-latex@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-latex/-/dict-latex-4.0.3.tgz#a1254c7d9c3a2d70cd6391a9f2f7694431b1b2cb"
integrity sha512-2KXBt9fSpymYHxHfvhUpjUFyzrmN4c4P8mwIzweLyvqntBT3k0YGZJSriOdjfUjwSygrfEwiuPI1EMrvgrOMJw==
"@cspell/dict-lorem-ipsum@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.3.tgz#c5fc631d934f1daf8b10c88b795278701a2469ec"
integrity sha512-WFpDi/PDYHXft6p0eCXuYnn7mzMEQLVeqpO+wHSUd+kz5ADusZ4cpslAA4wUZJstF1/1kMCQCZM6HLZic9bT8A==
"@cspell/dict-lua@^4.0.6":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@cspell/dict-lua/-/dict-lua-4.0.6.tgz#7de412bfaead794445e26d566aec222e20ad69ba"
integrity sha512-Jwvh1jmAd9b+SP9e1GkS2ACbqKKRo9E1f9GdjF/ijmooZuHU0hPyqvnhZzUAxO1egbnNjxS/J2T6iUtjAUK2KQ==
"@cspell/dict-makefile@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-makefile/-/dict-makefile-1.0.3.tgz#08d3349bf7cbd8f5dacf8641f3d35092ca0b8b38"
integrity sha512-R3U0DSpvTs6qdqfyBATnePj9Q/pypkje0Nj26mQJ8TOBQutCRAJbr2ZFAeDjgRx5EAJU/+8txiyVF97fbVRViw==
"@cspell/dict-markdown@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@cspell/dict-markdown/-/dict-markdown-2.0.7.tgz#15d6f9eed6bd1b33921b4332426ff387961163f1"
integrity sha512-F9SGsSOokFn976DV4u/1eL4FtKQDSgJHSZ3+haPRU5ki6OEqojxKa8hhj4AUrtNFpmBaJx/WJ4YaEzWqG7hgqg==
"@cspell/dict-monkeyc@^1.0.9":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.9.tgz#58b5f6f15fc7c11ce0eeffd0742fba4b39fc0b8b"
integrity sha512-Jvf6g5xlB4+za3ThvenYKREXTEgzx5gMUSzrAxIiPleVG4hmRb/GBSoSjtkGaibN3XxGx5x809gSTYCA/IHCpA==
"@cspell/dict-node@^5.0.5":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@cspell/dict-node/-/dict-node-5.0.5.tgz#11653612ebdd833208432e8b3cbe61bd6dd35dc3"
integrity sha512-7NbCS2E8ZZRZwlLrh2sA0vAk9n1kcTUiRp/Nia8YvKaItGXLfxYqD2rMQ3HpB1kEutal6hQLVic3N2Yi1X7AaA==
"@cspell/dict-npm@^5.1.17":
version "5.1.20"
resolved "https://registry.yarnpkg.com/@cspell/dict-npm/-/dict-npm-5.1.20.tgz#5e54b428d7609267263d426d0edc9d51d53f0a6f"
integrity sha512-vE9pFIifCDChsVhhUDuVtnwxygOdtHNluDm+8FkgC84M6LwiUVJr/CuSOI/SCR0oI9iiFp0VvMz194B6XwMv3g==
"@cspell/dict-people-names@^1.1.1":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-people-names/-/dict-people-names-1.1.4.tgz#e5a9bf51f0d6aae7f51cd1f2d2eb218f8f5bdba8"
integrity sha512-YgnstmeGj/FoIEX996w6SpzTHyNHNxBinwbgozciEo4KJEQkdR7K1GIvJJdRh5Bh81T7xY7PCb6l4nStCFyclA==
"@cspell/dict-php@^4.0.13":
version "4.0.13"
resolved "https://registry.yarnpkg.com/@cspell/dict-php/-/dict-php-4.0.13.tgz#86f1e6fb2174b2b0fa012baf86c448b2730f04f9"
integrity sha512-P6sREMZkhElzz/HhXAjahnICYIqB/HSGp1EhZh+Y6IhvC15AzgtDP8B8VYCIsQof6rPF1SQrFwunxOv8H1e2eg==
"@cspell/dict-powershell@^5.0.13":
version "5.0.13"
resolved "https://registry.yarnpkg.com/@cspell/dict-powershell/-/dict-powershell-5.0.13.tgz#f557aa04ee9bda4fe091308a0bcaea09ed12fa76"
integrity sha512-0qdj0XZIPmb77nRTynKidRJKTU0Fl+10jyLbAhFTuBWKMypVY06EaYFnwhsgsws/7nNX8MTEQuewbl9bWFAbsg==
"@cspell/dict-public-licenses@^2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.11.tgz#37550c4e0cd445991caba528bf4ba58ce7a935c3"
integrity sha512-rR5KjRUSnVKdfs5G+gJ4oIvQvm8+NJ6cHWY2N+GE69/FSGWDOPHxulCzeGnQU/c6WWZMSimG9o49i9r//lUQyA==
"@cspell/dict-python@^4.2.13":
version "4.2.13"
resolved "https://registry.yarnpkg.com/@cspell/dict-python/-/dict-python-4.2.13.tgz#c3dbaa7e2434c835e11540345e2168e5e685190a"
integrity sha512-mZIcmo9qif8LkJ6N/lqTZawcOk2kVTcuWIUOSbMcjyomO0XZ7iWz15TfONyr03Ea/l7o5ULV+MZ4vx76bAUb7w==
dependencies:
"@cspell/dict-data-science" "^2.0.5"
"@cspell/dict-r@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-r/-/dict-r-2.0.4.tgz#31b5abd91cc12aebfffdde4be4d2902668789311"
integrity sha512-cBpRsE/U0d9BRhiNRMLMH1PpWgw+N+1A2jumgt1if9nBGmQw4MUpg2u9I0xlFVhstTIdzXiLXMxP45cABuiUeQ==
"@cspell/dict-ru_ru@^2.2.1":
version "2.2.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-ru_ru/-/dict-ru_ru-2.2.4.tgz#a0b4a9272ca360b1721ef999e02559dc636d4043"
integrity sha512-Ub5Y318ZAaFJDAPgeImcLg8ksfthGhxMHsyHGkn9Uf3g9AZUlYsabs1HwgLmh9NtqDNjMlF52S9R11GFDdaWIw==
"@cspell/dict-ruby@^5.0.7":
version "5.0.7"
resolved "https://registry.yarnpkg.com/@cspell/dict-ruby/-/dict-ruby-5.0.7.tgz#3593a955baaffe3c5d28fb178b72fdf93c7eec71"
integrity sha512-4/d0hcoPzi5Alk0FmcyqlzFW9lQnZh9j07MJzPcyVO62nYJJAGKaPZL2o4qHeCS/od/ctJC5AHRdoUm0ktsw6Q==
"@cspell/dict-rust@^4.0.10":
version "4.0.10"
resolved "https://registry.yarnpkg.com/@cspell/dict-rust/-/dict-rust-4.0.10.tgz#8ae6eaf31a0ebce9dc8fd8dd68e5925e1d5290ee"
integrity sha512-6o5C8566VGTTctgcwfF3Iy7314W0oMlFFSQOadQ0OEdJ9Z9ERX/PDimrzP3LGuOrvhtEFoK8pj+BLnunNwRNrw==
"@cspell/dict-scala@^5.0.6":
version "5.0.6"
resolved "https://registry.yarnpkg.com/@cspell/dict-scala/-/dict-scala-5.0.6.tgz#5e925def2fe6dc27ee2ad1c452941c3d6790fb6d"
integrity sha512-tl0YWAfjUVb4LyyE4JIMVE8DlLzb1ecHRmIWc4eT6nkyDqQgHKzdHsnusxFEFMVLIQomgSg0Zz6hJ5S1E4W4ww==
"@cspell/dict-software-terms@^4.1.19", "@cspell/dict-software-terms@^4.1.3":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@cspell/dict-software-terms/-/dict-software-terms-4.2.0.tgz#c16e9d3326c36195dadea812bc035817054bf0c1"
integrity sha512-cTLTNdP9RM6nruZ01FThEFKRi7C4TPN8ndc+FpvCqis9J8iSg4Cr4YQemT/DxXoXz0527NbBPCARunxA0qIgTA==
"@cspell/dict-sql@^2.1.8":
version "2.1.8"
resolved "https://registry.yarnpkg.com/@cspell/dict-sql/-/dict-sql-2.1.8.tgz#45ea53b3e57fd2cc5f839f49b644aa743dac4990"
integrity sha512-dJRE4JV1qmXTbbGm6WIcg1knmR6K5RXnQxF4XHs5HA3LAjc/zf77F95i5LC+guOGppVF6Hdl66S2UyxT+SAF3A==
"@cspell/dict-svelte@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@cspell/dict-svelte/-/dict-svelte-1.0.5.tgz#09752e01ff6667e737566d9dfc704c8dcc9a6492"
integrity sha512-sseHlcXOqWE4Ner9sg8KsjxwSJ2yssoJNqFHR9liWVbDV+m7kBiUtn2EB690TihzVsEmDr/0Yxrbb5Bniz70mA==
"@cspell/dict-swift@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-swift/-/dict-swift-2.0.4.tgz#bc19522418ed68cf914736b612c4e4febbf07e8d"
integrity sha512-CsFF0IFAbRtYNg0yZcdaYbADF5F3DsM8C4wHnZefQy8YcHP/qjAF/GdGfBFBLx+XSthYuBlo2b2XQVdz3cJZBw==
"@cspell/dict-terraform@^1.0.6":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@cspell/dict-terraform/-/dict-terraform-1.0.7.tgz#815a523c86f647cb7695d48b69e4a793c49f875e"
integrity sha512-Ip7tOlAt/qUVdWYyDMA7DlKMpQ6sjtrsXk4vcpqXoYpoJlzMoDce7pw+fPhHshtNOFBAZ4nOrszlLu6APuy+HQ==
"@cspell/dict-typescript@^3.1.11":
version "3.1.11"
resolved "https://registry.yarnpkg.com/@cspell/dict-typescript/-/dict-typescript-3.1.11.tgz#40586f13b0337bd9cba958e0661b35888580b249"
integrity sha512-FwvK5sKbwrVpdw0e9+1lVTl8FPoHYvfHRuQRQz2Ql5XkC0gwPPkpoyD1zYImjIyZRoYXk3yp9j8ss4iz7A7zoQ==
"@cspell/dict-uk-ua@^4.0.1":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@cspell/dict-uk-ua/-/dict-uk-ua-4.0.4.tgz#19ecb8eaa70a363fd53a2adab0ea462f6e57e055"
integrity sha512-4n+ImjsKVWkcZC9O/zQY4oZ9KWm0qkyTZY5/aW1KZsP8sx0cK5IUaA/375R3i4l8SDc0WDZN8K6Wy71cggDUEA==
"@cspell/dict-vue@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@cspell/dict-vue/-/dict-vue-3.0.3.tgz#295c288f6fd363879898223202ec3be048663b98"
integrity sha512-akmYbrgAGumqk1xXALtDJcEcOMYBYMnkjpmGzH13Ozhq1mkPF4VgllFQlm1xYde+BUKNnzMgPEzxrL2qZllgYA==
"@cspell/dict-win32@^2.0.3":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@cspell/dict-win32/-/dict-win32-2.0.6.tgz#3edaa576906061584b470aca1b41b6d76548eeca"
integrity sha512-znBvliYtgXIpMW1/T/VnrvA2CwEqfJd06I57laVQsa/ST7dUeUU2kTbk4A4ICCr3eemVMPl9NQtR+t3SQu+ogQ==
"@cspell/dynamic-import@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/dynamic-import/-/dynamic-import-8.17.1.tgz#2b3f3325b6013a067a1a49cda8b69ae73aaed36a"
integrity sha512-XQtr2olYOtqbg49E+8SISd6I5DzfxmsKINDn0ZgaTFeLalnNdF3ewDU4gOEbApIzGffRa1mW9t19MsiVrznSDw==
dependencies:
"@cspell/url" "8.17.1"
import-meta-resolve "^4.1.0"
"@cspell/filetypes@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/filetypes/-/filetypes-8.17.1.tgz#d193afc5029364334f005ff23f4c4cb80170c374"
integrity sha512-AxYw6j7EPYtDFAFjwybjFpMc9waXQzurfBXmEVfQ5RQRlbylujLZWwR6GnMqofeNg4oGDUpEjcAZFrgdkvMQlA==
"@cspell/strong-weak-map@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/strong-weak-map/-/strong-weak-map-8.17.1.tgz#2fa88f283ef10222fad25134b5ebb54edaad985f"
integrity sha512-8cY3vLAKdt5gQEMM3Gr57BuQ8sun2NjYNh9qTdrctC1S9gNC7XzFghTYAfHSWR4VrOUcMFLO/izMdsc1KFvFOA==
"@cspell/url@8.17.1":
version "8.17.1"
resolved "https://registry.yarnpkg.com/@cspell/url/-/url-8.17.1.tgz#e7daec1597fa31b4d0a7a685e7a24a11b0c8a193"
integrity sha512-LMvReIndW1ckvemElfDgTt282fb2C3C/ZXfsm0pJsTV5ZmtdelCHwzmgSBmY5fDr7D66XDp8EurotSE0K6BTvw==
array-timsort@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926"
integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==
braces@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.1.1"
callsites@^3.0.0, callsites@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
chalk-template@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-1.1.0.tgz#ffc55db6dd745e9394b85327c8ac8466edb7a7b1"
integrity sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==
dependencies:
chalk "^5.2.0"
chalk@^5.2.0, chalk@^5.3.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
clear-module@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/clear-module/-/clear-module-4.1.2.tgz#5a58a5c9f8dccf363545ad7284cad3c887352a80"
integrity sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==
dependencies:
parent-module "^2.0.0"
resolve-from "^5.0.0"
commander@^12.1.0:
version "12.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
comment-json@^4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.2.5.tgz#482e085f759c2704b60bc6f97f55b8c01bc41e70"
integrity sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==
dependencies:
array-timsort "^1.0.3"
core-util-is "^1.0.3"
esprima "^4.0.1"
has-own-prop "^2.0.0"
repeat-string "^1.6.1"
core-util-is@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cspell-config-lib@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-config-lib/-/cspell-config-lib-8.17.1.tgz#a87973b91d51bf23a2018042c25aeaaa8a4e69c0"
integrity sha512-x1S7QWprgUcwuwiJB1Ng0ZTBC4G50qP9qQyg/aroMkcdMsHfk26E8jUGRPNt4ftHFzS4YMhwtXuJQ9IgRUuNPA==
dependencies:
"@cspell/cspell-types" "8.17.1"
comment-json "^4.2.5"
yaml "^2.6.1"
cspell-dictionary@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-dictionary/-/cspell-dictionary-8.17.1.tgz#bfc9bfdbd3720d1425260a98091acffab7b03dd5"
integrity sha512-zSl9l3wii+x16yc2NVZl/+CMLeLBAiuEd5YoFkOYPcbTJnfPwdjMNcj71u7wBvNJ+qwbF+kGbutEt15yHW3NBw==
dependencies:
"@cspell/cspell-pipe" "8.17.1"
"@cspell/cspell-types" "8.17.1"
cspell-trie-lib "8.17.1"
fast-equals "^5.0.1"
cspell-gitignore@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-gitignore/-/cspell-gitignore-8.17.1.tgz#38f3213a40ba86480bb5f66a91198db6e0ef37c0"
integrity sha512-bk727Zf4FBCjm9Mwvyreyhgjwe+YhPQEW7PldkHiinKd+Irfez4s8GXLQb1EgV0UpvViqaqBqLmngjZdS30BTA==
dependencies:
"@cspell/url" "8.17.1"
cspell-glob "8.17.1"
cspell-io "8.17.1"
find-up-simple "^1.0.0"
cspell-glob@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-glob/-/cspell-glob-8.17.1.tgz#23d1be46b32fb4933487e4edff347d34db446f5a"
integrity sha512-cUwM5auSt0RvLX7UkP2GEArJRWc85l51B1voArl+3ZIKeMZwcJpJgN3qvImtF8yRTZwYeYCs1sgsihb179q+mg==
dependencies:
"@cspell/url" "8.17.1"
micromatch "^4.0.8"
cspell-grammar@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-grammar/-/cspell-grammar-8.17.1.tgz#8f6619fbfaebff6aeee63b13d17898b4d0c09136"
integrity sha512-H5tLcBuW7aUj9L0rR+FSbnWPEsWb8lWppHVidtqw9Ll1CUHWOZC9HTB2RdrhJZrsz/8DJbM2yNbok0Xt0VAfdw==
dependencies:
"@cspell/cspell-pipe" "8.17.1"
"@cspell/cspell-types" "8.17.1"
cspell-io@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-io/-/cspell-io-8.17.1.tgz#b91f1cac1c64a6fa2b61a388d0dc67437fcf3ada"
integrity sha512-liIOsblt7oVItifzRAbuxiYrwlgw1VOqKppMxVKtYoAn2VUuuEpjCj6jLWpoTqSszR/38o7ChsHY1LHakhJZmw==
dependencies:
"@cspell/cspell-service-bus" "8.17.1"
"@cspell/url" "8.17.1"
cspell-lib@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-lib/-/cspell-lib-8.17.1.tgz#21c76f1ea4e91c90245e55acddbf452d055a6607"
integrity sha512-66n83Q7bK5tnvkDH7869/pBY/65AKmZVfCOAlsbhJn3YMDbNHFCHR0d1oNMlqG+n65Aco89VGwYfXxImZY+/mA==
dependencies:
"@cspell/cspell-bundled-dicts" "8.17.1"
"@cspell/cspell-pipe" "8.17.1"
"@cspell/cspell-resolver" "8.17.1"
"@cspell/cspell-types" "8.17.1"
"@cspell/dynamic-import" "8.17.1"
"@cspell/filetypes" "8.17.1"
"@cspell/strong-weak-map" "8.17.1"
"@cspell/url" "8.17.1"
clear-module "^4.1.2"
comment-json "^4.2.5"
cspell-config-lib "8.17.1"
cspell-dictionary "8.17.1"
cspell-glob "8.17.1"
cspell-grammar "8.17.1"
cspell-io "8.17.1"
cspell-trie-lib "8.17.1"
env-paths "^3.0.0"
fast-equals "^5.0.1"
gensequence "^7.0.0"
import-fresh "^3.3.0"
resolve-from "^5.0.0"
vscode-languageserver-textdocument "^1.0.12"
vscode-uri "^3.0.8"
xdg-basedir "^5.1.0"
cspell-trie-lib@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell-trie-lib/-/cspell-trie-lib-8.17.1.tgz#618e5cc671b0a24cf7ec27a9a9b834b197e17392"
integrity sha512-13WNa5s75VwOjlGzWprmfNbBFIfXyA7tYYrbV+LugKkznyNZJeJPojHouEudcLq3SYb2Q6tJ7qyWcuT5bR9qPA==
dependencies:
"@cspell/cspell-pipe" "8.17.1"
"@cspell/cspell-types" "8.17.1"
gensequence "^7.0.0"
cspell@^8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/cspell/-/cspell-8.17.1.tgz#be3c79a5b0b2e374ac0df8f921eb30ddca170110"
integrity sha512-D0lw8XTXrTycNzOn5DkfPJNUT00X53OgvFDm+0SzhBr1r+na8LEh3CnQ6zKYVU0fL0x8vU82vs4jmGjDho9mPg==
dependencies:
"@cspell/cspell-json-reporter" "8.17.1"
"@cspell/cspell-pipe" "8.17.1"
"@cspell/cspell-types" "8.17.1"
"@cspell/dynamic-import" "8.17.1"
"@cspell/url" "8.17.1"
chalk "^5.3.0"
chalk-template "^1.1.0"
commander "^12.1.0"
cspell-dictionary "8.17.1"
cspell-gitignore "8.17.1"
cspell-glob "8.17.1"
cspell-io "8.17.1"
cspell-lib "8.17.1"
fast-json-stable-stringify "^2.1.0"
file-entry-cache "^9.1.0"
get-stdin "^9.0.0"
semver "^7.6.3"
tinyglobby "^0.2.10"
env-paths@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-3.0.0.tgz#2f1e89c2f6dbd3408e1b1711dd82d62e317f58da"
integrity sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==
esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
fast-equals@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d"
integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==
fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fdir@^6.4.2:
version "6.4.2"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689"
integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==
file-entry-cache@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-9.1.0.tgz#2e66ad98ce93f49aed1b178c57b0b5741591e075"
integrity sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==
dependencies:
flat-cache "^5.0.0"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
find-up-simple@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.0.tgz#21d035fde9fdbd56c8f4d2f63f32fd93a1cfc368"
integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==
flat-cache@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-5.0.0.tgz#26c4da7b0f288b408bb2b506b2cb66c240ddf062"
integrity sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==
dependencies:
flatted "^3.3.1"
keyv "^4.5.4"
flatted@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27"
integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==
gensequence@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/gensequence/-/gensequence-7.0.0.tgz#bb6aedec8ff665e3a6c42f92823121e3a6ea7718"
integrity sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==
get-stdin@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575"
integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==
global-directory@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/global-directory/-/global-directory-4.0.1.tgz#4d7ac7cfd2cb73f304c53b8810891748df5e361e"
integrity sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==
dependencies:
ini "4.1.1"
has-own-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-2.0.0.tgz#f0f95d58f65804f5d218db32563bb85b8e0417af"
integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==
import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-meta-resolve@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706"
integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==
ini@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1"
integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
keyv@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
dependencies:
json-buffer "3.0.1"
micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies:
braces "^3.0.3"
picomatch "^2.3.1"
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
dependencies:
callsites "^3.0.0"
parent-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-2.0.0.tgz#fa71f88ff1a50c27e15d8ff74e0e3a9523bf8708"
integrity sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==
dependencies:
callsites "^3.1.0"
picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-from@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
semver@^7.6.3:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
tinyglobby@^0.2.10:
version "0.2.10"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f"
integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==
dependencies:
fdir "^6.4.2"
picomatch "^4.0.2"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
vscode-languageserver-textdocument@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631"
integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==
vscode-uri@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
xdg-basedir@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-5.1.0.tgz#1efba19425e73be1bc6f2a6ceb52a3d2c884c0c9"
integrity sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==
yaml@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773"
integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==

View File

@@ -578,7 +578,7 @@
"type": "victoriametrics-datasource",
"uid": "$ds"
},
"expr": "sum(vm_persistentqueue_bytes_pending{job=~\"$job\", instance=~\"$instance\"})",
"expr": "sum(vmagent_remotewrite_pending_data_bytes{job=~\"$job\", instance=~\"$instance\"})",
"interval": "",
"legendFormat": "",
"refId": "A"

View File

@@ -577,7 +577,7 @@
"type": "prometheus",
"uid": "$ds"
},
"expr": "sum(vm_persistentqueue_bytes_pending{job=~\"$job\", instance=~\"$instance\"})",
"expr": "sum(vmagent_remotewrite_pending_data_bytes{job=~\"$job\", instance=~\"$instance\"})",
"interval": "",
"legendFormat": "",
"refId": "A"

View File

@@ -2,9 +2,9 @@
DOCKER_NAMESPACE ?= victoriametrics
ROOT_IMAGE ?= alpine:3.21.0
ROOT_IMAGE ?= alpine:3.21.2
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.21.0
CERTS_IMAGE := alpine:3.21.2
GO_BUILDER_IMAGE := golang:1.23.4-alpine
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
@@ -66,6 +66,12 @@ package-via-docker: package-base
$(DOCKER_BUILD) \
--build-arg src_binary=$(APP_NAME)$(APP_SUFFIX)-prod \
--build-arg base_image=$(BASE_IMAGE) \
--label "org.opencontainers.image.source=https://github.com/VictoriaMetrics/VictoriaMetrics" \
--label "org.opencontainers.image.documentation=https://docs.victoriametrics.com/" \
--label "org.opencontainers.image.title=$(APP_NAME)" \
--label "org.opencontainers.image.vendor=VictoriaMetrics" \
--label "org.opencontainers.image.version=$(PKG_TAG)" \
--label "org.opencontainers.image.created=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ")" \
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(APP_SUFFIX)$(RACE) \
-f app/$(APP_NAME)/deployment/Dockerfile bin)
@@ -80,6 +86,12 @@ publish-via-docker:
--build-arg certs_image=$(CERTS_IMAGE) \
--build-arg root_image=$(ROOT_IMAGE) \
--build-arg APP_NAME=$(APP_NAME) \
--label "org.opencontainers.image.source=https://github.com/VictoriaMetrics/VictoriaMetrics" \
--label "org.opencontainers.image.documentation=https://docs.victoriametrics.com/" \
--label "org.opencontainers.image.title=$(APP_NAME)" \
--label "org.opencontainers.image.vendor=VictoriaMetrics" \
--label "org.opencontainers.image.version=$(PKG_TAG)" \
--label "org.opencontainers.image.created=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ")" \
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) \
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(LATEST_TAG)$(RACE) \
-o type=image \
@@ -92,6 +104,12 @@ publish-via-docker:
--build-arg certs_image=$(CERTS_IMAGE) \
--build-arg root_image=$(ROOT_IMAGE_SCRATCH) \
--build-arg APP_NAME=$(APP_NAME) \
--label "org.opencontainers.image.source=https://github.com/VictoriaMetrics/VictoriaMetrics" \
--label "org.opencontainers.image.documentation=https://docs.victoriametrics.com/" \
--label "org.opencontainers.image.title=$(APP_NAME)" \
--label "org.opencontainers.image.vendor=VictoriaMetrics" \
--label "org.opencontainers.image.version=$(PKG_TAG)" \
--label "org.opencontainers.image.created=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ")" \
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE)-scratch \
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(LATEST_TAG)$(RACE)-scratch \
-o type=image \

View File

@@ -4,7 +4,7 @@ services:
# And forward them to --remoteWrite.url
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.1
depends_on:
- "vminsert"
ports:
@@ -39,7 +39,7 @@ services:
# where N is number of vmstorages (2 in this case).
vmstorage-1:
container_name: vmstorage-1
image: victoriametrics/vmstorage:v1.108.1-cluster
image: victoriametrics/vmstorage:v1.109.1-cluster
ports:
- 8482
- 8400
@@ -51,7 +51,7 @@ services:
restart: always
vmstorage-2:
container_name: vmstorage-2
image: victoriametrics/vmstorage:v1.108.1-cluster
image: victoriametrics/vmstorage:v1.109.1-cluster
ports:
- 8482
- 8400
@@ -66,7 +66,7 @@ services:
# pre-process them and distributes across configured vmstorage shards.
vminsert:
container_name: vminsert
image: victoriametrics/vminsert:v1.108.1-cluster
image: victoriametrics/vminsert:v1.109.1-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -81,7 +81,7 @@ services:
# vmselect collects results from configured `--storageNode` shards.
vmselect-1:
container_name: vmselect-1
image: victoriametrics/vmselect:v1.108.1-cluster
image: victoriametrics/vmselect:v1.109.1-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -94,7 +94,7 @@ services:
restart: always
vmselect-2:
container_name: vmselect-2
image: victoriametrics/vmselect:v1.108.1-cluster
image: victoriametrics/vmselect:v1.109.1-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -112,7 +112,7 @@ services:
# It can be used as an authentication proxy.
vmauth:
container_name: vmauth
image: victoriametrics/vmauth:v1.108.1
image: victoriametrics/vmauth:v1.109.1
depends_on:
- "vmselect-1"
- "vmselect-2"
@@ -127,7 +127,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.1
depends_on:
- "vmauth"
ports:

View File

@@ -45,7 +45,7 @@ services:
# storing logs and serving read queries.
victorialogs:
container_name: victorialogs
image: victoriametrics/victoria-logs:v1.4.0-victorialogs
image: victoriametrics/victoria-logs:v1.6.1-victorialogs
command:
- "--storageDataPath=/vlogs"
- "--httpListenAddr=:9428"
@@ -60,7 +60,7 @@ services:
# scraping, storing metrics and serve read requests.
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.1
ports:
- 8428:8428
volumes:
@@ -79,7 +79,7 @@ services:
# depending on the requested path.
vmauth:
container_name: vmauth
image: victoriametrics/vmauth:v1.108.1
image: victoriametrics/vmauth:v1.109.1
depends_on:
- "victoriametrics"
- "victorialogs"
@@ -96,7 +96,7 @@ services:
# vmalert executes alerting and recording rules according to given rule type.
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.1
depends_on:
- "vmauth"
- "alertmanager"

View File

@@ -4,7 +4,7 @@ services:
# And forward them to --remoteWrite.url
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.1
depends_on:
- "victoriametrics"
ports:
@@ -22,7 +22,7 @@ services:
# storing metrics and serve read requests.
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.1
ports:
- 8428:8428
- 8089:8089
@@ -65,7 +65,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.1
depends_on:
- "victoriametrics"
- "alertmanager"

Some files were not shown because too many files have changed in this diff Show More