Compare commits

..

196 Commits

Author SHA1 Message Date
Zakhar Bessarab
c157576c2f app/vmalert/notifier: fix test after b1e998f7
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-02-06 15:02:47 +04:00
Zakhar Bessarab
b1e998f7ad app/vmalert/utils: unregister metrics only if there is no refs left
Currently, when performing rules reload vmalert treats interval change as a new group. This leads to previous group being closed.
When group is closed it also unregisters metrics related to the group.

The problem is that newly created group will still use metrics with the same names as name only includes "file" and "group name" as labels and these are the same.

This commit introduces a "reference tracking" for metric names and prevents unregistering metrics if metric name is still in use.

See: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8229
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-02-06 14:49:26 +04:00
Aliaksandr Valialkin
171d4019cd lib/logstorage: properly limit the number of concurrent workers at stats, top and uniq pipes according to the provided options(concurrency=N)
The number of worker shards per each pipe processor is created during query initialization.
This number equals to the `options(concurrency=N)` if this option is set or to the number of available CPU cores.
This means that all the pipes must adhere the given concurrency when passing data blocks
to the next pipe.

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8201

The bug has been introduced in 0214aa328e
2025-02-06 09:16:56 +01:00
Github Actions
54f60d804a Automatic update operator docs from VictoriaMetrics/operator@96519dc (#8235)
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-02-05 22:48:03 +01:00
Nikolay
78dc9533fc app/vmauth: allow to serve internal API and different address
vmauth uses 'lib/httpserver' for serving HTTP requests. This server
unconditionally defines built-in routes (such as '/metrics',
'/health', etc). It makes impossible to proxy `HTTP` requests to  backends with the same routes.
Since vmauth's httpserver matches built-in route and return local
response.

 This commit adds new flag `httpInternalListenAddr` with
default empty value. Which removes internal API routes from public
router and exposes it at separate http server.

For example given configuration disables private routes at `0.0.0.0:8427` address and serves it at `0.0.0.0:8426`:

`./bin/vmauth --auth.config=config.yaml --httpListenAddr=:8427 --httpInternalListenAddr=127.0.0.1:8426`

Related issues:
- https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6468
- https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7345
2025-02-05 17:10:11 +01:00
Joost Buskermolen
088e06c2c2 app/vmselect: expose /-/healthy and /-/ready endpoints on full Prometheus path
This commit improves integration with third-party solutions who rely on non-root
endpoints (i.e. MinIO) when the vmselect path has been specified in the
configured Prometheus URL like:
`http://vmselect.:8481/select/0/prometheus`

Comparable change has been done before
(b885a3b6e9), however only takes care of
the root path. This means endpoints `-/healthy` and `-/ready` are still
not available on full vmselect Prometheus paths, resulting in
unsupported path requests.

This change makes these endpoints available on the full paths like:
`/select/0/prometheus/-/healthy` and `/select/0/prometheus/-/ready`,
thus achieving full Prometheus compatibility for external dependencies.

Related issues:
- https://github.com/minio/console/issues/2829
- https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1833

---

Signed-off-by: Joost Buskermolen <j.buskermolen@cloudmeesters.com>
2025-02-05 17:00:52 +01:00
hagen1778
553e95e293 docs: fix copy&paste typo in change type
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-05 15:29:58 +01:00
Github Actions
46fbb95b16 Automatic update operator docs from VictoriaMetrics/operator@8192989 (#8207)
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-02-05 15:24:10 +01:00
Github Actions
fbe592f495 Automatic update helm docs from VictoriaMetrics/helm-charts@0155ba2 (#8217)
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-02-05 15:23:10 +01:00
Artem Fetishev
631b736bc2 lib/storage: fix cardinality limiting for cases when insertion takes fast path (#8218)
### Describe Your Changes

The cardinality limiter in this case does not receive the actual
metricID but some other value found in r.TSID.MetricID and is not
initialized. Depending on the system and/or go runtime implementation,
this value can be 0 or some garbage value (which shouldn't have too wide
a range). Thus, there basically no limit for inserted metricIDs.

### 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>
Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2025-02-05 15:22:34 +01:00
Fred Navruzov
ceefa406cc docs/vmanomaly: fix 404 links (#8219)
### Describe Your Changes

- fixed recently found 404 under `/anomaly-detection/` docs section
- get rid of remaining .html / .index.html endings in links under
`/anomaly-detection/` docs section

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-02-04 20:37:46 +02:00
Zakhar Bessarab
2748681f40 {docs,app/vmctl}: clarify how vm-rate-limit is applied
### Describe Your Changes

Explicitly mention that `--vm-rate-limit` is used for each individual
worker defined by `--vm-concurrency`.

### 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-02-04 18:22:54 +04:00
Zakhar Bessarab
e2cfd351c0 docs/streaming-aggregation: fix typo in metric name
### Describe Your Changes

Fix metric name labels set not being "closed" by `}`.

### 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-02-04 13:09:11 +04:00
f41gh7
a2b49a1843 app/vmagent/kafka: properly close producer client
At v1.107.0 release with commit 21a7f06fc4beeb6ad32b9f7fd88704ed33674905 was introduced regression.

It prevents kafka producer client from graceful shutdown. This change also removed kafka message headers.

 This commit properly closes client.

 It also addresses the following issues:

* properly add headers to kafka message
* add url_id to the exported metrics, which prevents possible metrics clash if multiple remote write targets have
 the same url and topic name.

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-02-03 14:21:52 +01:00
hagen1778
a70e8e0379 docs: mention tlast_over_time in influx guide
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8188

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-01 23:17:24 +01:00
hagen1778
894a2f91e6 docs: fix typo in image name
Follow-up after 35d77a3bed

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-01 23:14:08 +01:00
Artem Fetishev
30af662d84 RemoteWriteConnectionIsSaturated alert: add another saturation cause to the alert description (#8195)
### Describe Your Changes

Currently the alert descrption considers only one end of the connection
(vmagent). While saturation can also be caused by slowness of the
receiving components (vminsert, vmstorage). Update the alert description
with a brief suggestion to also check the dashboards of these
components.

### 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>
Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2025-02-01 22:23:26 +01:00
Roman Khavronenko
fa2107bbec metricsql: bump to v0.83.0 (#8141)
metricsql: bump to v0.83.0

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7703

The update also returns an error if metric name is specified twice in
metrics selector.
For example, `foo{__name__="bar"}` is not allowed anymore. It would
successfully parse before
this change, but it won't satisfy the search filter any way. So it had
no sense in supporting this. This is why some test cases were removed.

Signed-off-by: hagen1778 <roman@victoriametrics.com>

### 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-02-01 22:22:29 +01:00
Jay
1427ea3ca9 docs: fix typo in docker examples readme (#8194)
### Describe Your Changes

Fixed misspelling of the word "components" from "componetns"

### Checklist

The following checks are **mandatory**:

- [X] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-02-01 22:13:24 +01:00
hagen1778
5561970db0 app/vmalert: mention that remoteWrite.concurrency depends on CPU
Mnetion explicitly that `remoteWrite.concurrency` deopends on number
of available CPU cores. Updated docs to rm auto-printed default value.

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8151

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-01 22:10:47 +01:00
Github Actions
4d42e29967 Automatic update operator docs from VictoriaMetrics/operator@7930b3a (#8202)
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-31 16:46:45 +01:00
Roman Khavronenko
72837919ae app/vmselect/promql: fix discrepancies when using or binary operator
The change covers various corner cases when using `or` binary operator.
See corresponding issues and pull request here to see the cases:
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7770

Related issues:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7759
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7640


---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-31 14:14:17 +01:00
Zakhar Bessarab
6166149474 docs/release-guide: add a note for vmui build (#8183)
### 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-31 16:57:54 +04:00
Github Actions
f34e82eb9b Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@de3daa7 (#8196) 2025-01-31 16:57:30 +04:00
Github Actions
2545082c0f Automatic update helm docs from VictoriaMetrics/helm-charts@485594e (#8197)
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-31 16:57:17 +04:00
Github Actions
676669020f Automatic update Grafana datasource docs from VictoriaMetrics/victoriametrics-datasource@35e86ad (#8190) 2025-01-31 16:57:04 +04:00
Zakhar Bessarab
071db473a7 docs/victoria-logs/quickstart: use link to docs for chart (#8186)
### 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-31 16:56:43 +04:00
Andrii Chubatiuk
94ac051647 fixed cloud docs links (#8198)
### Describe Your Changes

fixed some cloud docs links

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-31 16:56:21 +04:00
Zakhar Bessarab
3419c56eef deployment/docker: install VL datasource from marketplace (#8199)
### 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-31 16:23:11 +04:00
Github Actions
3091133a36 Automatic update operator docs from VictoriaMetrics/operator@946c30e (#8193)
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-30 18:38:50 +01:00
Roman Khavronenko
63a1baf10f deployment: use signed metrics datasource (#8189)
* use signed datasource
* bump Grafana because on v10 pre-installing+provisioning didn't work
* consistently rename provisioning folder

### 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-30 16:01:00 +01:00
Zakhar Bessarab
64a3c13c2f app/vmui/make: add a step to enforce license type override
- update license file content
- add a build step to enforce setting correct license type

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-30 14:34:43 +01:00
hagen1778
0b801a147e dashboards: improve wording for SlowInserts panel info
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-30 14:20:28 +01:00
f41gh7
a45afa4cd8 docs/changelog: add recommendation for lts releases upgrade
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-30 11:22:06 +01:00
hagen1778
552f0ce466 docs: fix broken vminsert link in changelog
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-30 10:25:11 +01:00
Yury Molodov
f3012c4a85 vmui/logs: add _msg to group view field list (#8177)
### Describe Your Changes

add the `_msg` field to the list of fields for the group view, allowing
users to select multiple fields, including `_msg`, for log display.

![image](https://github.com/user-attachments/assets/90336315-8708-424d-9ed8-aa8d9a0d07a9)

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-29 16:18:08 +01:00
Github Actions
2589065329 Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@b532af3 (#8181) 2025-01-29 16:13:54 +01:00
Yury Molodov
777f05efcc vmui: refactor clipboard handling with useCopyToClipboard hook (#8178)
### Describe Your Changes

Replaced `await navigator.clipboard.writeText` with `useCopyToClipboard`
hook.
See [this pull
request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7778)
for details.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-29 16:13:31 +01:00
Yury Molodov
03a1883525 vmui/logs: fix dropdown menu in group view settings (#8176)
### Describe Your Changes

Fixed an issue where dropdown menus were not visible in the Group View
settings.
Ref issue: #8153 

### Checklist

The following checks are **mandatory**:

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

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2025-01-29 16:12:13 +01:00
Dmitry Konishchev
17fd53bd37 app/vmselect: properly cancel long running requests on client connection close
At this time `bufferedwriter` [silently ignores connection close
errors](78eaa056c0/lib/bufferedwriter/bufferedwriter.go (L67)).
It may be very convenient in some situations (to not log such
unimportant errors), but it's too implicit and unsafe for the others.
For example, if you close [export
API](https://docs.victoriametrics.com/#how-to-export-time-series) client
connection in the middle of communication, VictoriaMetrics won't notice
it and will start to hog CPU by exporting all the data into nowhere
until it process all of them. If you'll make a few retries, it will be
effectively a DoS on the server.

This commit replaces this implicit error suppressing with explicit error
handling which fixes the issue with export API.

Issue was introduced at e78f3ac8ac
2025-01-29 16:09:32 +01:00
Yury Molodov
34204cc597 vmui/logs: fix transparency for bars in hits chart (#8174)
### Describe Your Changes

Restored transparency for bars in the hits chart to improve visibility.
Ref issue: #8152

| State  | Preview |
|--------|---------|
| Before |
![image](https://github.com/user-attachments/assets/2f95cdd9-0b39-4420-9bf7-011274eadc86)
|
| After |
![image](https://github.com/user-attachments/assets/9123d8e7-e615-41ab-9193-4c50ce262e7f)
|

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-29 16:02:20 +01:00
Yury Molodov
e22d47b85c vmui: add heatmap-to-line chart switch (#8169)
### Describe Your Changes

- Added support for switching from heatmap to line chart (the switch is
displayed only for heatmap data).
- Added a warning for heatmap rendering issues with a button to switch
to a line chart.
- Fixed a chart rendering issue when switching from heatmap to line
chart.

Ref issue: #8057 

<img
src="https://github.com/user-attachments/assets/2efc901a-3dcd-4b5b-aeaa-8f22b2dcedaa"
width="600"/>
<hr/>
<img
src="https://github.com/user-attachments/assets/2594cbb6-8356-4448-9576-0d7da4749256"
width="600"/>
<hr/>
<img
src="https://github.com/user-attachments/assets/ccfeb8b4-7e98-41a7-90f4-ee9dcf971141"
width="600"/>

### Checklist

The following checks are **mandatory**:

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

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2025-01-29 15:50:15 +01:00
Github Actions
0e08a7a125 Automatic update Grafana datasource docs from VictoriaMetrics/victoriametrics-datasource@0d6e500 (#8180) 2025-01-29 13:27:11 +01:00
Roman Khavronenko
13c4324bb5 lib/cgroup: warn users about using fractional CPU quotas (#8175)
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7988

### 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-29 13:19:08 +01:00
Aliaksandr Valialkin
95f182053b lib/logstorage: remove unnecesary abstraction - RowsFormatter
It is better to use the AppendFieldsToJSON function directly
instead of hiding it under RowsFormatter abstraction.
2025-01-28 18:03:18 +01:00
Aliaksandr Valialkin
ec64a1fd7c docs/Articles.md: add a link to https://tanmay-bhat.medium.com/reducing-inter-az-traffic-in-victoriametrics-with-zonekeeper-3bd7e1526796
Thanks to Tanmay Bhat ( https://tanmay-bhat.medium.com/ ) for the great article about VictoriaMetrics and vmagent!
2025-01-28 17:03:45 +01:00
Aliaksandr Valialkin
f61b5da617 go.mod: update Go from v1.23.5 to 1.23.5
This is a follow-up for 489631b227
2025-01-28 16:55:48 +01:00
Aliaksandr Valialkin
3c036e0d31 lib/logstorage: ignore logs with too long field names during data ingestion
Previously too long field names were silently truncated. This is not what most users expect.
It is better ignoring the whole log entry in this case and logging it with the WARNING message,
so human operator could notice and fix the ingestion of incorrect logs ASAP.

The commit also adds and updates the following entries to VictoriaLogs faq:

- https://docs.victoriametrics.com/victorialogs/faq/#how-many-fields-a-single-log-entry-may-contain
- https://docs.victoriametrics.com/victorialogs/faq/#what-is-the-maximum-supported-field-name-length
- https://docs.victoriametrics.com/victorialogs/faq/#what-length-a-log-record-is-expected-to-have

These entries are referred at `-insert.maxLineSizeBytes` and `-insert.maxFieldsPerLine` command-line descriptions
and at the WARNING messages, which are emitted when log entries are ignored because of some of these limits
are exceeded.
2025-01-28 16:55:48 +01:00
Aliaksandr Valialkin
aacb9b2726 app/vlogscli: show compact output mode line in the help output to be consistent with the naming for other output modes 2025-01-28 16:55:47 +01:00
Alexander Marshalov
c753f75e18 vmcloud docs: actualize parameters description due to changes in v1.108.0 (#6928) (#8172)
vmcloud docs: actualize parameters description due to changes in
v1.108.0 (#6928)
2025-01-28 16:05:07 +01:00
f41gh7
330461c635 docs/changelog: mention v1.102.12 and v1.97.17 releases
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-28 14:27:44 +01:00
Roman Khavronenko
5a1a28ba87 dashboards: consistently use process_cpu_cores_available for CPU usage
Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7988

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-28 13:58:06 +01:00
hagen1778
5b18dc0214 docs: re-order changes for better readability
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-28 09:57:15 +01:00
Aliaksandr Valialkin
10abdf34ab vendor: run make vendor-update 2025-01-27 23:10:31 +01:00
Fred Navruzov
e55b5fc605 docs/vmanomaly: release v1.19.2 (#8165)
### Describe Your Changes

docs/vmanomaly: release v1.19.2 (patch that addresses some of the bugs
found in 1.19.0)

### Checklist

The following checks are **mandatory**:

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

---------

Co-authored-by: Mathias Palmersheim <mathias@victoriametrics.com>
2025-01-27 21:03:34 +02:00
Aliaksandr Valialkin
3b34241380 lib/fs/fsutil: move lib/envutil to the more appropriate place at lib/fs/fsutil
This is a follow-up for 043d066133
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6871
2025-01-27 18:47:39 +01:00
Zakhar Bessarab
de144899b2 docs/{vmauth,VictoriaLogs}: add examples of using headers per-user
### Describe Your Changes
- add example a generic example to vmauth docs
- add an multi-tenancy usage example to VictoriaLogs docs
### 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-27 20:14:43 +04:00
Aliaksandr Valialkin
dc9dba71b2 lib/storage: open per-month partitions in parallel
This should reduce the time needed for opening the storage with retentions exceeding a few months.

While at at, limit the concurrency of opening partitions in parallel to the number of available CPU cores,
since higher concurrency may increase RAM usage and CPU usage without performance improvements
if opening a single partition is CPU-bound task.

This is a follow-up for 17988942ab
2025-01-27 16:07:14 +01:00
Aliaksandr Valialkin
fd1568531d lib/filestream: use correct formatting option for error type in the error message 2025-01-27 15:23:52 +01:00
f41gh7
46bb2d4308 docs/changelog: mention lts releases
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-27 11:05:50 +01:00
f41gh7
8fa8101677 docs: point apps versions to the latest releases
v1.110.0
v1.102.11
v1.97.16

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-27 11:05:50 +01:00
f41gh7
204cec7913 CHANGELOG.md: cut v1.110.0 release 2025-01-27 11:05:50 +01:00
Aliaksandr Valialkin
17988942ab lib/logstorage: open per-day partitions in parallel during startup
This significantly reduces startup times when the storage contains large partitions over many days.
2025-01-27 00:34:02 +01:00
Aliaksandr Valialkin
ed05ae12c4 lib/logstorage: optimize unmarshalColumnNames a bit
This should reduce the time needed for opening a large storage with many partitions,
which contain logs with big number of fields (aka wide events).

Thanks to @kiriklo for the initial idea at the pull request https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8061

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7937
2025-01-27 00:12:46 +01:00
Aliaksandr Valialkin
256924e2d6 lib/logstorage: improve error message by adding a link with the explanation why VictoriaLogs ignores logs with the size exceeding 2MB
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7972
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7984
2025-01-26 22:50:26 +01:00
Aliaksandr Valialkin
b5392337bf lib/logstorage: block_stat pipe: return the path to the part where the block is stored 2025-01-26 22:36:47 +01:00
Aliaksandr Valialkin
fa4e3607c3 docs/VictoriaLogs: small updates 2025-01-26 22:03:21 +01:00
Aliaksandr Valialkin
043d066133 lib/{fs,filestream}: unconditionally disable fsync in tests
Use the testing.Testing() function in order to determine whether the code runs in test.
This allows running tests and fast speed without the need to specify DISABLE_FSYNC_FOR_TESTING
environment variable.

This is a follow-up for the commit 334cd92a6c
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6871
2025-01-26 21:47:20 +01:00
Aliaksandr Valialkin
900159a2d3 lib/logstorage: remove unneeded code after 202eb429a7
readerWithStats isn't used when reading column names from file
2025-01-26 20:04:15 +01:00
Aliaksandr Valialkin
4464c5a254 docs/VictoriaLogs/sql-to-logsql.md: pay attention to the fact that stats() pipe at LogsQL has better usability than GROUP BY at SQL 2025-01-24 19:58:37 +01:00
Aliaksandr Valialkin
90fed18b83 deployment/docker: update VictoriaLogs from v1.7.0-victorialogs to v1.8.0-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.8.0-victorialogs
2025-01-24 19:16:27 +01:00
Aliaksandr Valialkin
1480ecc129 docs/VictoriaLogs/CHANGELOG.md: cut v1.8.0-victorialogs release 2025-01-24 19:06:44 +01:00
Aliaksandr Valialkin
c67f4d4d86 app/vlselect/vmui: run make vmui-logs-update after the commit 87739bbbef 2025-01-24 19:05:47 +01:00
Aliaksandr Valialkin
c2f5088adc docs/VictoriaLogs/CHANGELOG.md: move the changes from 87739bbbef to the correct place
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7750
2025-01-24 19:04:35 +01:00
Yury Molodov
87739bbbef vmui/logs: update hits chart legend (#7750)
### Describe Your Changes

- Added the `fields_limit` parameter for the `hits` query to limit the
number of returned fields. This reduces the response size and decreases
memory usage.

#### Legend Menu:
A context menu has been added for legend items, which includes:  
1. Metric name
2. **Copy _stream name** - copies the full stream name in the format
`{field1="value1", ..., fieldN="valueN"}`.
3. **Add _stream to filter** - adds the full stream value to the current
filter:
   `_stream: {field1="value1", ..., fieldN="valueN"} AND (old_expr)`.  
4. **Exclude _stream from filter** - excludes the stream from the
current filter:
`(NOT _stream: {field1="value1", ..., fieldN="valueN"}) AND (old_expr)`.
5. List of fields with options:  
   - Copy as `field: "value"`.  
   - Add to filter: `field: "value" AND (old_expr)`.  
   - Exclude from filter: `-field: "value" AND (old_expr)`.  
6. Total number of hits for the stream.  

Related issue: #7552

<details>
  <summary>UI Demo - Legend Menu</summary>
  
<img width="400"
src="https://github.com/user-attachments/assets/ee1954b2-fdce-44b4-a2dc-aa73096a5414"/>
<img width="400"
src="https://github.com/user-attachments/assets/19d71f04-c207-4143-a176-c5f221592e3d"/>

</details>

---

#### Legend:
1. Displays the total number of hits for the stream.  
2. Added hints below the legend for total hits and graph interactions.
3. Click behavior is now the same as in other vmui charts:  
   - `click` - shows only the selected series.  
   - `click + ctrl/cmd` - hides the selected series.  
   
<details>
  <summary>UI Demo - Legend</summary>

before: 
<img
src="https://github.com/user-attachments/assets/18270842-0c39-4f63-bcda-da62e15c3c73"/>

after:
<img
src="https://github.com/user-attachments/assets/351cad3a-f763-4b1d-b3be-b569b5472a7c"/>
  
</details>

---

#### Tooltip:  
1. The `other` label is moved to the end, and others are sorted by
value.
2. Values are aligned to the right.  
3. Labels are truncated and always shown in a single line for better
readability; the full name is available in the legend.

<details>
  <summary>UI Demo - Tooltip</summary>
  
| before     | after        |
|----------|----------|
| <img
src="https://github.com/user-attachments/assets/adccff38-e2e6-46e4-a69e-21381982489c"/>
| <img
src="https://github.com/user-attachments/assets/81008897-d816-4aed-92cb-749ea7e0ff1e"/>
|
  
</details>

---

#### Group View (tab Group):
Groups are now sorted by the number of records in descending order.

<details>
  <summary>UI Demo - Group View</summary>

before: 
<img width="800"
src="https://github.com/user-attachments/assets/15b4ca72-7e5d-421f-913b-c5ff22c340cb"/>

after:
<img width="800"
src="https://github.com/user-attachments/assets/32ff627b-6f30-4195-bfe7-8c9b4aa11f6b"/>
  
</details>
2025-01-24 19:02:04 +01:00
Aliaksandr Valialkin
dce4dc0a33 docs/VictoriaLogs/CHANGELOG.md: typo fix after ad6c587494: ignore_global_time_range -> ignore_global_time_filter 2025-01-24 18:59:40 +01:00
Aliaksandr Valialkin
ad6c587494 lib/logstorage: properly propagate extra filters to all the subqueries
The purpose of extra filters ( https://docs.victoriametrics.com/victorialogs/querying/#extra-filters )
is to limit the subset of logs, which can be queried. For example, it is expected that all the queries
with `extra_filters={tenant=123}` can access only logs, which contain `123` value for the `tenant` field.

Previously this wasn't the case, since the provided extra filters weren't applied to subqueries.
For example, the following query could be used to select all the logs outside `tenant=123`, for any `extra_filters` arg:

    * | union({tenant!=123})

This commit fixes this by propagating extra filters to all the subqueries.

While at it, this commit also properly propagates [start, end] time range filter from HTTP querying APIs
into all the subqueries, since this is what most users expect. This behaviour can be overriden on per-subquery
basis with the `options(ignore_global_time_filter=true)` option - see https://docs.victoriametrics.com/victorialogs/logsql/#query-options

Also properly apply apply optimizations across all the subqueries. Previously the optimizations at Query.optimize()
function were applied only to the top-level query.
2025-01-24 18:49:25 +01:00
Aliaksandr Valialkin
467cdd8a3d lib: consistently use logger.Panicf("BUG: ...") for logging programming bugs
logger.Fatalf("BUG: ...") complicates investigating the bug, since it doesn't show the call stack,
which led to the bug. So it is better to consistently use logger.Panicf("BUG: ...") for logging programming bugs.
2025-01-24 16:39:21 +01:00
Aliaksandr Valialkin
026894054b docs/VictoriaLogs/LogsQL.md: show how to unroll the returned histogram buckets into separate rows at histogram pipe docs 2025-01-24 16:39:21 +01:00
Aliaksandr Valialkin
db9107acef docs/VictoriaLogs/sql-to-logsql.md: show how to substitute complex SQL query with top pipe 2025-01-24 16:39:20 +01:00
f41gh7
f8a0f2fe44 make vmui-update 2025-01-24 14:11:28 +01:00
Nikolay
bfd83e3cca app/vmselect: fixes panic data race at query tracing
Previously, NewChild elements of querytracer could be referenced by concurrent
storageNode goroutines. After earlier return ( if search.skipSlowReplicas is set), it is
possible, that tracer objects could be still in-use by concurrent workers.
  It may cause panics and data races. Most probable case is when parent tracer is finished, but children
still could write data to itself via Donef() method. It triggers read-write data race at trace
formatting.

This commit adds a new methods to the querytracer package, that allows to
create children not referenced by parent and add it to the parent later.

 Orphaned child must be registered at the parent, when goroutine returns. It's done synchronously by the single caller  via finishQueryTracer call.
If child didn't finished work and reference for it is used by concurrent goroutine, new child must be created instead with
context message.
 It prevents panics and possible data races.

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

---------

Signed-off-by: f41gh7 <nik@victoriametrics.com>
Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2025-01-24 13:56:09 +01:00
Roman Khavronenko
6b20ec9c7d docs: add avaialbe_version notion to new -search.maxDeleteDuration (#8142)
Follow-up for
4574958e2e

### 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-24 13:08:40 +01:00
Yury Molodov
f0d55a1c25 vmui: save column settings in URL #7662 (#7979)
### Describe Your Changes

Added saving column settings in the URL for the table view. See #7662

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2025-01-24 09:50:08 +01:00
Yury Molodov
f31dece58d vmui: fix issue with query execution and line breaks in query editor
This commit fixes incorrect behaviour when pressing `Enter` did not execute the query, and
`Shift+Enter` did not insert a new line.

- The issue occurred when autocomplete was disabled.  
- This problem affected the query editor in both the VictoriaMetrics UI
and VictoriaLogs UI.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8058
2025-01-24 08:41:10 +01:00
Dmytro Kozlov
a6951b8b14 deployment/docker: upgraded Grafana plugins to the latest versions
Upgraded Grafana plugins to the latest versions
2025-01-24 08:38:35 +01:00
Zakhar Bessarab
d56e3df770 app/vmselect/prometheus: fix panic when performing delete with "multitenant" auth token
Initially delete_series API wasn't implemented for mulitenant auth token.

 This commit fixes it and properly handle delete series requests for mulitenant auth token.
It also adds integration tests for this case.

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

Introduced at v1.104.0 release:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1434
---------

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
Co-authored-by: f41gh7 <nik@victoriametrics.com>
2025-01-24 08:36:52 +01:00
Zakhar Bessarab
d88c1fbdbb app/vmselect/prometheus: prevent panic when using "multitenant" at /api/v1/series/count requests
Adding support of multi-tenant reads to /api/v1/series/count would
require introducing a breaking change to a `netstorage` RPC, so
currently vmselect will explicitly deny these requests.

Related issues:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8126

Introduced at v1.104.0 release:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1434

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-24 08:36:38 +01:00
Phuong Le
a947ccf228 lib/logstorage: remove redundant error check 2025-01-24 07:51:10 +01:00
Aliaksandr Valialkin
5747e8b5d0 docs/VictoriaLogs/sql-to-logsql.md: add a guide on how to convert SQL to LogsQL 2025-01-24 04:35:19 +01:00
Aliaksandr Valialkin
aab0174c94 docs/VictoriaLogs/README.md: add a link to VictoriaLogs playground 2025-01-24 04:29:07 +01:00
Aliaksandr Valialkin
2c271aa9b2 docs/VictoriaLogs/LogsQL.md: mention that field pipe can be used for improving query performance 2025-01-23 23:36:37 +01:00
Aliaksandr Valialkin
7cdeb3a32c lib/logstorage: inherit query options by nested queries
This is a follow-up for b620b5cff5
2025-01-23 22:15:37 +01:00
hagen1778
8c4ac815cb deployment: reflect metrics datasource ID change
See https://github.com/VictoriaMetrics/victoriametrics-datasource

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-23 15:18:22 +01:00
Roman Khavronenko
dcb6dd5dcb app/vmselect/promql: respect staleness in removeCounterResets (#8073)
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8072

### 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>
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2025-01-23 14:31:32 +01:00
Zhu Jiekun
be24fbe8ae docs/vmagent: estimating the message size and rate when using Kafka
When using Kafka, it's important to estimate the message rate and size
before applying for resources.

This commit  explains why and how to use remote write metrics to
do the evaluation.
2025-01-23 11:25:47 +01:00
Jose Gómez-Sellés
fc1a89f51c doc: fix typo in vmsingle k8s monitoring guide (#8120)
As reported by a user, the value in [VictoriaMetrics Single helm
chart](https://github.com/VictoriaMetrics/helm-charts/blob/master/charts/victoria-metrics-single/values.yaml#L420)
and actual example, should be "enabled", not "enable". This commit fixes
it.


### 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/).
2025-01-23 08:59:09 +01:00
Aliaksandr Valialkin
eddeccfcfb lib/logstorage: add hash pipe for calculating hash over the given log field
This pipe may be useful for sharding log entries among hash buckets.
2025-01-23 04:16:46 +01:00
Aliaksandr Valialkin
b620b5cff5 lib/logstorage: add an ability to set query concurrency on a per-query basis
This is done via 'options(concurrency=N)' prefix for the query.
For example, the following query is executed on at most 4 CPU cores:

    options(concurrency=4) _time:1d | count_uniq(user_id)

This allows reducing RAM and CPU usage at the cost of longer query execution times,
since by default every query is executed in parallel on all the available CPU cores.

See https://docs.victoriametrics.com/victorialogs/logsql/#query-options
2025-01-23 02:42:16 +01:00
Aliaksandr Valialkin
42c21ff671 lib/logstorage: always pass the current timestamp to newLexer()
Also always initialize Query.timestamp with the timestamp from the lexer.

This should avoid potential problems with relative timestamps inside inner queries.
For example, the `_time:1h` filter in the following query is correctly executed
relative to the current timestamp:

   foo:in(_time:1h | keep foo)
2025-01-23 02:42:16 +01:00
Aliaksandr Valialkin
b9eb9fe72d lib/logstorage: simplify the caller side of addNewItem() function 2025-01-23 02:42:16 +01:00
Daria Karavaieva
9ae49b405c docs/vmanomaly: format & fix docs (#8122)
### Describe Your Changes

- fix formatting in Presets
- change integration guide config according to new format + optimisation
of prophet
- minor fixes

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-22 18:59:51 +01:00
Daria Karavaieva
f932deb47a docs/vmanomaly: prod ready config example (#8107)
### Describe Your Changes

- provide prod ready config examples for vmanomaly
- minor formating fix

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-22 13:49:52 +01:00
Zhu Jiekun
77f446d095 docs: add curl file upload example to csv import doc (#8113)
### Describe Your Changes

Provide `curl` file upload example to csv import related section.

https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8080

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-22 13:48:25 +01:00
Github Actions
5f8810fc8d Automatic update helm docs from VictoriaMetrics/helm-charts@b26141b (#8116)
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-22 13:47:55 +01:00
Nikolay
80ead7cfa4 app/vmauth: remove readTrackingBody pool (#8104)
Sync.Pool for readTrackingBody was added in order to reduce potential
load on garbage collector. But golang net/http standard library does not
allow to reuse request body, since it closes body asynchronously after
return. Related issue: https://github.com/golang/go/issues/51907

This commit removes sync.Pool in order to fix potential panic and data
race at requests processing.

 Affected releases:
- all releases after v1.97.7

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

Signed-off-by: f41gh7 <nik@victoriametrics.com>
Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2025-01-22 13:32:23 +01:00
Zakhar Bessarab
8772288bd6 docs/guides/guide-vmcluster-multiple-retention-setup: fix some typos (#8112)
### Describe Your Changes

- fix typo in text
- fix typos in schema, re-create schema and upload source

Current doc:
https://docs.victoriametrics.com/guides/guide-vmcluster-multiple-retention-setup/

Here is an updated schema preview:

![image](https://github.com/user-attachments/assets/a65613c0-f3f6-4df5-bc70-fd70f1677386)


### 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-22 10:02:32 +01:00
Zakhar Bessarab
338095fdd3 lib/license: trim whitespaces before license key validation
Validation expects license key to be present without any addition whitespaces, so having a trailing whitespaces would lead to verification failure.

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-22 12:06:40 +04:00
Fred Navruzov
299d66fd98 docs/vmanomaly: release v1.19.1 (#8108)
### Describe Your Changes

docs/vmanomaly: release v1.19.1

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-21 20:42:47 +02:00
Dmytro Kozlov
77218c5848 deployment/docker: update datasource plugins version to the latest release (#8106)
### Describe Your Changes

Updated plugins to the latest versions

### Checklist

The following checks are **mandatory**:

- [X] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-21 20:39:31 +04:00
hagen1778
9e6fc9269d docs: move changelog line to the corresponding section
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-21 15:46:38 +01:00
hagen1778
661f9fc3e2 docs: rm misleading default value for -maxConcurrentInserts
See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5494
and https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5494#issuecomment-2603067137

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-21 12:50:14 +01:00
Andrii Chubatiuk
2adb5fe014 lib/protoparser/opentelemetry: do not drop histogram buckets, when sum is absent (#8054)
Despite requirement in OpenTelemetry spec that histograms should contain
sum, [OpenTelemetry collector promremotewrite
translator](37c8044abf/pkg/translator/prometheusremotewrite/helper.go (L222))
and [Prometheus OpenTelemetry
parsing](d52e689a20/storage/remote/otlptranslator/prometheusremotewrite/helper.go (L264))
skip only sum if it's absent. Our current implementation drops buckets
if sum is absent, which causes issues for users, that are expecting a
similar to Prometheus behaviour

### 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/).

---------

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2025-01-21 12:46:55 +01:00
Github Actions
ce917a4cc3 Automatic update operator docs from VictoriaMetrics/operator@5c7959f (#8102)
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-21 11:36:42 +01:00
Github Actions
b3de1c029c Automatic update helm docs from VictoriaMetrics/helm-charts@21bf09b (#8098)
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-21 13:27:13 +04:00
Fred Navruzov
461c7a5ad7 docs/vmanomaly: release v1.19.0 (#8097)
### Describe Your Changes

Updated `vmanomaly` docs to release v1.19.0. Also, slight improvements
on other pages, like FAQ

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-21 10:20:09 +04:00
Aliaksandr Valialkin
489631b227 deployment/docker: update Go builder from Go1.23.4 to Go1.23.5
See https://github.com/golang/go/issues?q=milestone%3AGo1.23.5+label%3ACherryPickApproved
2025-01-20 21:51:13 +01:00
Aliaksandr Valialkin
e78ff0dc2a deployment/docker: update VictoriaLogs from v1.6.1-victorialogs to v1.7.0-victorialogs 2025-01-20 21:47:02 +01:00
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
42 changed files with 1658 additions and 2185 deletions

View File

@@ -2,19 +2,20 @@ package insertutils
import (
"fmt"
"math"
"strconv"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
)
// ExtractTimestampFromFields extracts timestamp in nanoseconds from the field with the name timeField at fields.
// ExtractTimestampRFC3339NanoFromFields extracts RFC3339 timestamp in nanoseconds from the field with the name timeField at fields.
//
// The value for the timeField is set to empty string after returning from the function,
// so it could be ignored during data ingestion.
//
// The current timestamp is returned if fields do not contain a field with timeField name or if the timeField value is empty.
func ExtractTimestampFromFields(timeField string, fields []logstorage.Field) (int64, error) {
func ExtractTimestampRFC3339NanoFromFields(timeField string, fields []logstorage.Field) (int64, error) {
for i := range fields {
f := &fields[i]
if f.Name != timeField {
@@ -47,24 +48,22 @@ func parseTimestamp(s string) (int64, error) {
return nsecs, nil
}
// ParseUnixTimestamp parses s as unix timestamp in seconds, milliseconds, microseconds or nanoseconds and returns the parsed timestamp in nanoseconds.
// ParseUnixTimestamp parses s as unix timestamp in either seconds or milliseconds and returns the parsed timestamp in nanoseconds.
func ParseUnixTimestamp(s string) (int64, error) {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse unix timestamp from %q: %w", s, err)
}
if n < (1<<31) && n >= (-1<<31) {
// The timestamp is in seconds.
return n * 1e9, nil
// The timestamp is in seconds. Convert it to milliseconds
n *= 1e3
}
if n < 1e3*(1<<31) && n >= 1e3*(-1<<31) {
// The timestamp is in milliseconds.
return n * 1e6, nil
if n > int64(math.MaxInt64)/1e6 {
return 0, fmt.Errorf("too big timestamp in milliseconds: %d; mustn't exceed %d", n, int64(math.MaxInt64)/1e6)
}
if n < 1e6*(1<<31) && n >= 1e6*(-1<<31) {
// The timestamp is in microseconds.
return n * 1e3, nil
if n < int64(math.MinInt64)/1e6 {
return 0, fmt.Errorf("too small timestamp in milliseconds: %d; must be bigger than %d", n, int64(math.MinInt64)/1e6)
}
// The timestamp is in nanoseconds
n *= 1e6
return n, nil
}

View File

@@ -6,11 +6,11 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
)
func TestExtractTimestampFromFields_Success(t *testing.T) {
func TestExtractTimestampRFC3339NanoFromFields_Success(t *testing.T) {
f := func(timeField string, fields []logstorage.Field, nsecsExpected int64) {
t.Helper()
nsecs, err := ExtractTimestampFromFields(timeField, fields)
nsecs, err := ExtractTimestampRFC3339NanoFromFields(timeField, fields)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -51,18 +51,6 @@ func TestExtractTimestampFromFields_Success(t *testing.T) {
{Name: "foo", Value: "bar"},
}, 1718773640123456789)
// Unix timestamp in nanoseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
{Name: "time", Value: "1718773640123456789"},
}, 1718773640123456789)
// Unix timestamp in microseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
{Name: "time", Value: "1718773640123456"},
}, 1718773640123456000)
// Unix timestamp in milliseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
@@ -76,14 +64,14 @@ func TestExtractTimestampFromFields_Success(t *testing.T) {
}, 1718773640000000000)
}
func TestExtractTimestampFromFields_Error(t *testing.T) {
func TestExtractTimestampRFC3339NanoFromFields_Error(t *testing.T) {
f := func(s string) {
t.Helper()
fields := []logstorage.Field{
{Name: "time", Value: s},
}
nsecs, err := ExtractTimestampFromFields("time", fields)
nsecs, err := ExtractTimestampRFC3339NanoFromFields("time", fields)
if err == nil {
t.Fatalf("expecting non-nil error")
}
@@ -92,7 +80,6 @@ func TestExtractTimestampFromFields_Error(t *testing.T) {
}
}
// invalid time
f("foobar")
// incomplete time

View File

@@ -99,7 +99,7 @@ func readLine(lr *insertutils.LineReader, timeField string, msgFields []string,
if err := p.ParseLogMessage(line); err != nil {
return false, fmt.Errorf("cannot parse json-encoded log entry: %w", err)
}
ts, err := insertutils.ExtractTimestampFromFields(timeField, p.Fields)
ts, err := insertutils.ExtractTimestampRFC3339NanoFromFields(timeField, p.Fields)
if err != nil {
return false, fmt.Errorf("cannot get timestamp: %w", err)
}

View File

@@ -560,7 +560,7 @@ func processLine(line []byte, currentYear int, timezone *time.Location, useLocal
if useLocalTimestamp {
ts = time.Now().UnixNano()
} else {
nsecs, err := insertutils.ExtractTimestampFromFields("timestamp", p.Fields)
nsecs, err := insertutils.ExtractTimestampRFC3339NanoFromFields("timestamp", p.Fields)
if err != nil {
return fmt.Errorf("cannot get timestamp from syslog line %q: %w", line, err)
}

View File

@@ -2,6 +2,7 @@ package notifier
import (
"context"
"fmt"
"testing"
"time"
@@ -27,10 +28,12 @@ func TestBlackHoleNotifier_Send(t *testing.T) {
}
func TestBlackHoleNotifier_Close(t *testing.T) {
addr := "blackhole-close"
bh := newBlackHoleNotifier()
bh.addr = addr
if err := bh.Send(context.Background(), []Alert{{
GroupID: 0,
Name: "alert0",
Name: "alert1",
Start: time.Now().UTC(),
End: time.Now().UTC(),
Annotations: map[string]string{"a": "b", "c": "d", "e": "f"},
@@ -41,10 +44,10 @@ func TestBlackHoleNotifier_Close(t *testing.T) {
bh.Close()
defaultMetrics := metricset.GetDefaultSet()
alertMetricName := "vmalert_alerts_sent_total{addr=\"blackhole\"}"
alertMetricName := fmt.Sprintf("vmalert_alerts_sent_total{addr=%q}", addr)
for _, name := range defaultMetrics.ListMetricNames() {
if name == alertMetricName {
t.Fatalf("Metric name should have unregistered.But still present")
t.Fatalf("Metric name should have unregistered. But still present")
}
}
}

View File

@@ -1,14 +1,56 @@
package utils
import "github.com/VictoriaMetrics/metrics"
import (
"sync"
"sync/atomic"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
)
type namedMetric struct {
Name string
}
var usedMetrics map[string]*atomic.Int64
var usedMetricMu sync.Mutex
func trackUsedMetric(name string) {
usedMetricMu.Lock()
defer usedMetricMu.Unlock()
if usedMetrics == nil {
usedMetrics = make(map[string]*atomic.Int64)
}
if _, ok := usedMetrics[name]; !ok {
usedMetrics[name] = &atomic.Int64{}
}
usedMetrics[name].Add(1)
}
// Unregister removes the metric by name from default registry
func (nm namedMetric) Unregister() {
metrics.UnregisterMetric(nm.Name)
if usedMetrics == nil {
logger.Fatalf("BUG: unregistered metric %q before registering", nm.Name)
}
usedMetricMu.Lock()
counter, ok := usedMetrics[nm.Name]
if !ok {
logger.Fatalf("BUG: unregistered metric %q before registering", nm.Name)
}
current := counter.Add(-1)
usedMetricMu.Unlock()
if current < 0 {
logger.Fatalf("BUG: negative metric counter for %q", nm.Name)
}
if current == 0 {
metrics.UnregisterMetric(nm.Name)
}
}
// Gauge is a metrics.Gauge with Name
@@ -19,6 +61,7 @@ type Gauge struct {
// GetOrCreateGauge creates a new Gauge with the given name
func GetOrCreateGauge(name string, f func() float64) *Gauge {
trackUsedMetric(name)
return &Gauge{
namedMetric: namedMetric{Name: name},
Gauge: metrics.GetOrCreateGauge(name, f),
@@ -33,6 +76,7 @@ type Counter struct {
// GetOrCreateCounter creates a new Counter with the given name
func GetOrCreateCounter(name string) *Counter {
trackUsedMetric(name)
return &Counter{
namedMetric: namedMetric{Name: name},
Counter: metrics.GetOrCreateCounter(name),
@@ -47,6 +91,7 @@ type Summary struct {
// GetOrCreateSummary creates a new Summary with the given name
func GetOrCreateSummary(name string) *Summary {
trackUsedMetric(name)
return &Summary{
namedMetric: namedMetric{Name: name},
Summary: metrics.GetOrCreateSummary(name),

View File

@@ -0,0 +1,52 @@
package utils
import (
"testing"
"github.com/VictoriaMetrics/metrics"
)
func isMetricRegistered(name string) bool {
metricNames := metrics.GetDefaultSet().ListMetricNames()
for _, mn := range metricNames {
if mn == name {
return true
}
}
return false
}
func TestMetricIsUnregistered(t *testing.T) {
metricName := "example_runs_total"
c := GetOrCreateCounter(metricName)
if !isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be present", metricName)
}
c.Unregister()
if isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be unregistered", metricName)
}
}
func TestMetricIsRemovedIfNoUses(t *testing.T) {
metricName := "example_runs_total"
c := GetOrCreateCounter(metricName)
c2 := GetOrCreateCounter(metricName)
if !isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be present", metricName)
}
c.Unregister()
// metric should still be registered since c2 is using it
if !isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be present", metricName)
}
c2.Unregister()
if isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be unregistered", metricName)
}
}

View File

@@ -8,6 +8,7 @@ import (
"sort"
"sync"
"sync/atomic"
"time"
"unsafe"
"github.com/VictoriaMetrics/metrics"
@@ -1001,7 +1002,9 @@ func ExportBlocks(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline sear
sr := getStorageSearch()
defer putStorageSearch(sr)
startTime := time.Now()
sr.Init(qt, vmstorage.Storage, tfss, tr, sq.MaxMetrics, deadline.Deadline())
indexSearchDuration.UpdateDuration(startTime)
// Start workers that call f in parallel on available CPU cores.
workCh := make(chan *exportWork, gomaxprocs*8)
@@ -1139,7 +1142,9 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
defer vmstorage.WG.Done()
sr := getStorageSearch()
startTime := time.Now()
maxSeriesCount := sr.Init(qt, vmstorage.Storage, tfss, tr, sq.MaxMetrics, deadline.Deadline())
indexSearchDuration.UpdateDuration(startTime)
type blockRefs struct {
brs []blockRef
}
@@ -1291,6 +1296,8 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
return &rss, nil
}
var indexSearchDuration = metrics.NewHistogram(`vm_index_search_duration_seconds`)
type blockRef struct {
partRef storage.PartRef
addr tmpBlockAddr

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.7fa18e1b.css",
"main.js": "./static/js/main.ba08300f.js",
"main.css": "./static/css/main.af583aad.css",
"main.js": "./static/js/main.1413b18d.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.7fa18e1b.css",
"static/js/main.ba08300f.js"
"static/css/main.af583aad.css",
"static/js/main.1413b18d.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.ba08300f.js"></script><link href="./static/css/main.7fa18e1b.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.1413b18d.js"></script><link href="./static/css/main.af583aad.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

@@ -1,4 +1,4 @@
FROM golang:1.23.6 AS build-web-stage
FROM golang:1.23.5 AS build-web-stage
COPY build /build
WORKDIR /build

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ ROOT_IMAGE ?= alpine:3.21.2
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.21.2
GO_BUILDER_IMAGE := golang:1.23.6-alpine
GO_BUILDER_IMAGE := golang:1.23.5-alpine
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
BASE_IMAGE := local/base:1.1.4-$(shell echo $(ROOT_IMAGE) | tr :/ __)-$(shell echo $(CERTS_IMAGE) | tr :/ __)
DOCKER ?= docker

View File

@@ -152,7 +152,7 @@ services:
# and distributes them according to --config.file.
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.28.0
image: prom/alertmanager:v0.27.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

@@ -126,7 +126,7 @@ services:
# and distributes them according to --config.file.
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.28.0
image: prom/alertmanager:v0.27.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

@@ -93,7 +93,7 @@ services:
# and distributes them according to --config.file.
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.28.0
image: prom/alertmanager:v0.27.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

@@ -89,7 +89,7 @@ services:
- "--licenseFile=/license"
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.28.0
image: prom/alertmanager:v0.27.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

@@ -18,7 +18,7 @@ services:
- vlogs
generator:
image: golang:1.23.6-alpine
image: golang:1.23.5-alpine
restart: always
working_dir: /go/src/app
volumes:

View File

@@ -2,7 +2,7 @@ version: '3'
services:
generator:
image: golang:1.23.6-alpine
image: golang:1.23.5-alpine
restart: always
working_dir: /go/src/app
volumes:

View File

@@ -58,7 +58,7 @@ services:
- ./vmsingle/promscrape.yml:/promscrape.yml
grafana:
image: grafana/grafana:11.5.0
image: grafana/grafana:9.2.7
depends_on: [vmsingle]
ports:
- 3000:3000

View File

@@ -16,12 +16,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
## tip
* FEATURE: [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/): improve performance for [`stats by (...) ...`](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe) by up to 30% when it is applied to big number of `by (...)` groups.
* FEATURE: [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/): improve performance for [`top` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe) by up to 30% when it is applied to big number of unique values.
* FEATURE: [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe): improve performance for [`count_uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq-stats) and [`count_uniq_hash`](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq_hash-stats) functions by up to 30% when they are applied to big number of unique values.
* FEATURE: [`block_stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#block_stats-pipe): return the path to the part where every data block is stored. The path to the part is returned in the `part_path` field. This allows investigating the distribution of data blocks among parts.
* FEATURE: reduce VictoriaLogs startup time by multiple times when it opens a large datastore with big [retention](https://docs.victoriametrics.com/victorialogs/#retention).
* FEATURE: [data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/): accept timestamps with microsecond and nanosecond precision at [`_time` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#time-field).
* FEATURE: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): add the `_msg` field to the list of fields for the group view, allowing users to select multiple fields, including `_msg`, for log display.
* BUGFIX: [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/): properly limit [`concurrency` query option](https://docs.victoriametrics.com/victorialogs/logsql/#query-options) for [`stats`](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe), [`uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#uniq-pipe) and [`top`](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe). This prevents from `runtime error: index out of range` panic. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8201).

View File

@@ -56,8 +56,7 @@ Otherwise the timestamp field must be in one of the following formats:
If timezone information is missing (for example, `2023-06-20 15:32:10`),
then the time is parsed in the local timezone of the host where VictoriaLogs runs.
- Unix timestamp in seconds, milliseconds, microseconds or nanoseconds. For example, `1686026893` (seconds), `1686026893735` (milliseconds),
`1686026893735321` (microseconds) or `1686026893735321098` (nanoseconds).
- Unix timestamp in seconds or in milliseconds. For example, `1686026893` (seconds) or `1686026893735` (milliseconds).
See [these docs](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) for details on fields,
which must be present in the ingested log messages.
@@ -112,8 +111,7 @@ Otherwise the timestamp field must be in one of the following formats:
If timezone information is missing (for example, `2023-06-20 15:32:10`),
then the time is parsed in the local timezone of the host where VictoriaLogs runs.
- Unix timestamp in seconds, milliseconds, microseconds or nanoseconds. For example, `1686026893` (seconds), `1686026893735` (milliseconds),
`1686026893735321` (microseconds) or `1686026893735321098` (nanoseconds).
- Unix timestamp in seconds or in milliseconds. For example, `1686026893` (seconds) or `1686026893735` (milliseconds).
See [these docs](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) for details on fields,
which must be present in the ingested log messages.

View File

@@ -146,8 +146,7 @@ The timestamp field must be in one of the following formats:
If timezone information is missing (for example, `2023-06-20 15:32:10`),
then the time is parsed in the local timezone of the host where VictoriaLogs runs.
- Unix timestamp in seconds, milliseconds, microseconds or nanoseconds. For example, `1686026893` (seconds), `1686026893735` (milliseconds),
`1686026893735321` (microseconds) or `1686026893735321098` (nanoseconds).
- Unix timestamp in seconds or in milliseconds. For example, `1686026893` (seconds) or `1686026893735` (milliseconds).
For example, the following [log entry](#data-model) contains valid timestamp with millisecond precision in the `_time` field:

View File

@@ -18,16 +18,6 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
## tip
* SECURITY: upgrade Go builder from Go1.23.5 to Go1.23.6. See the list of issues addressed in [Go1.23.6](https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved).
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): fix polluted alert messages when multiple Alertmanager instances are configured as notifiers with alert_relabel_configs.. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8040), and thanks to @evkuzin for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8258).
## [v1.111.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.111.0)
Released at 2025-02-07
**Update note 1: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/victoriametrics/) stop exposing `vm_index_search_duration_seconds` histogram metric. This metric records time spent on search operations in the index. It was introduced in [v1.56.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.56.0). However, this metric was used neither in dashboards nor in alerting rules. It also has high cardinality because index search operations latency can differ by 3 orders of magnitude. Hence, dropping it as unused.**
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/cluster-victoriametrics/): improve startup times when opening a storage with the [retention](https://docs.victoriametrics.com/#retention) exceeding a few months.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to switch the heatmap to a line chart. Now, vmui would suggest to switch to line graph display if heatmap can't be properly rendered. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8057).
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth/): add `-httpInternalListenAddr` cmd-line flag to serve internal HTTP routes `/metrics`, `/flags`, etc. It allows properly route requests to backends with the same service routes as vmauth. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6468) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7345) for details.
@@ -39,6 +29,7 @@ Released at 2025-02-07
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui) for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html) components: properly display enterprise features when the enterprise version is used.
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmselect](https://docs.victoriametrics.com/cluster-victoriametrics/): fix discrepancies when using `or` binary operator. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7759) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7640) issues for details.
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): properly update number of unique series for [cardinality limiter](https://docs.victoriametrics.com/#cardinality-limiter) on ingestion. Previously, limit could undercount the real number of the ingested unique series.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): do not unregister group metrics if the group is still in use. Previously, this could lead to group metrics being absent even though rules group is still running. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8229) for details.
## [v1.102.12](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.12)

View File

@@ -35,7 +35,7 @@ which means there is no need to define metric names or their labels in advance.
metrics anytime.
Actually, the metric name is also a label with a special name `__name__`.
The `__name__` key could be omitted {{% available_from "v1.111.0" %}} for simplicity. So the following series are identical:
The `__name__` key could be omitted {{% available_from "#" %}} for simplicity. So the following series are identical:
```
requests_total{path="/", code="200"}

View File

@@ -1086,7 +1086,7 @@ It is recommended protecting the following endpoints with authKeys:
* `/metrics` with `-metricsAuthKey` command-line flag, so unauthorized users couldn't access [vmauth metrics](#monitoring).
* `/debug/pprof` with `-pprofAuthKey` command-line flag, so unauthorized users couldn't access [profiling information](#profiling).
As an alternative, it's possible to serve internal API routes at the different listen address with command-line flag `-httpInternalListenAddr=127.0.0.1:8426`. {{% available_from "v1.111.0" %}}
As an alternative, it's possible to serve internal API routes at the different listen address with command-line flag `-httpInternalListenAddr=127.0.0.1:8426`. {{% available_from "#" %}}
`vmauth` also supports the ability to restrict access by IP - see [these docs](#ip-filters). See also [concurrency limiting docs](#concurrency-limiting).

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/VictoriaMetrics/VictoriaMetrics
go 1.23.6
go 1.23.5
// This is needed in order to avoid vmbackup and vmrestore binary size increase by 20MB
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8008

View File

@@ -27,15 +27,7 @@ type chunkedAllocator struct {
uniqValuesProcessors []statsUniqValuesProcessor
valuesProcessors []statsValuesProcessor
pipeStatsGroups []pipeStatsGroup
pipeStatsGroupMaps []pipeStatsGroupMap
statsProcessors []statsProcessor
statsCountUniqSets []statsCountUniqSet
statsCountUniqHashSets []statsCountUniqHashSet
hitsMaps []hitsMap
pipeStatsGroups []pipeStatsGroup
u64Buf []uint64
@@ -124,26 +116,6 @@ func (a *chunkedAllocator) newPipeStatsGroup() (p *pipeStatsGroup) {
return addNewItem(&a.pipeStatsGroups, a)
}
func (a *chunkedAllocator) newPipeStatsGroupMaps(itemsLen uint) []pipeStatsGroupMap {
return addNewItems(&a.pipeStatsGroupMaps, itemsLen, a)
}
func (a *chunkedAllocator) newStatsProcessors(itemsLen uint) []statsProcessor {
return addNewItems(&a.statsProcessors, itemsLen, a)
}
func (a *chunkedAllocator) newStatsCountUniqSets(itemsLen uint) []statsCountUniqSet {
return addNewItems(&a.statsCountUniqSets, itemsLen, a)
}
func (a *chunkedAllocator) newStatsCountUniqHashSets(itemsLen uint) []statsCountUniqHashSet {
return addNewItems(&a.statsCountUniqHashSets, itemsLen, a)
}
func (a *chunkedAllocator) newHitsMaps(itemsLen uint) []hitsMap {
return addNewItems(&a.hitsMaps, itemsLen, a)
}
func (a *chunkedAllocator) newUint64() (p *uint64) {
return addNewItem(&a.u64Buf, a)
}
@@ -153,32 +125,33 @@ func (a *chunkedAllocator) cloneBytesToString(b []byte) string {
}
func (a *chunkedAllocator) cloneString(s string) string {
xs := addNewItems(&a.stringsBuf, uint(len(s)), a)
copy(xs, s)
return bytesutil.ToUnsafeString(xs)
const maxChunkLen = 64 * 1024
if a.stringsBuf != nil && len(a.stringsBuf)+len(s) > maxChunkLen {
a.stringsBuf = nil
}
if a.stringsBuf == nil {
a.stringsBuf = make([]byte, 0, maxChunkLen)
a.bytesAllocated += maxChunkLen
}
sbLen := len(a.stringsBuf)
a.stringsBuf = append(a.stringsBuf, s...)
return bytesutil.ToUnsafeString(a.stringsBuf[sbLen:])
}
func addNewItem[T any](dstPtr *[]T, a *chunkedAllocator) *T {
xs := addNewItems(dstPtr, 1, a)
return &xs[0]
}
func addNewItems[T any](dstPtr *[]T, itemsLen uint, a *chunkedAllocator) []T {
dst := *dstPtr
var maxItems = (64 * 1024) / uint(unsafe.Sizeof(dst[0]))
if itemsLen > maxItems {
return make([]T, itemsLen)
}
if dst != nil && uint(len(dst))+itemsLen > maxItems {
var maxItems = (64 * 1024) / int(unsafe.Sizeof(dst[0]))
if dst != nil && len(dst)+1 > maxItems {
dst = nil
}
if dst == nil {
dst = make([]T, 0, maxItems)
a.bytesAllocated += int(maxItems * uint(unsafe.Sizeof(dst[0])))
a.bytesAllocated += maxItems * int(unsafe.Sizeof(dst[0]))
}
dstLen := uint(len(dst))
dst = dst[:dstLen+itemsLen]
xs := dst[dstLen : dstLen+itemsLen : dstLen+itemsLen]
var x T
dst = append(dst, x)
item := &dst[len(dst)-1]
*dstPtr = dst
return xs
return item
}

View File

@@ -9,176 +9,36 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
)
type hitsMapAdaptive struct {
type hitsMap struct {
stateSizeBudget *int
// concurrency is the number of parallel workers to use when merging shards.
//
// this field must be updated by the caller before using statsCountUniqProcessor.
concurrency uint
// hm tracks hits until the number of unique values reaches hitsMapAdaptiveMaxLen.
// After that hits are tracked by shards.
hm hitsMap
// shards tracks hits for big number of unique values.
//
// Every shard contains hits for a share of unique values.
shards []hitsMap
u64 map[uint64]*uint64
negative64 map[uint64]*uint64
strings map[string]*uint64
// a reduces memory allocations when counting the number of hits over big number of unique values.
a chunkedAllocator
}
// the maximum number of values to track in hitsMapAdaptive.hm before switching to hitsMapAdaptive.shards
//
// Too big value may slow down hitsMapMergeParallel() across big number of CPU cores.
// Too small value may significantly increase RAM usage when hits for big number of unique values are counted.
const hitsMapAdaptiveMaxLen = 4 << 10
func (hma *hitsMapAdaptive) reset() {
*hma = hitsMapAdaptive{}
}
func (hma *hitsMapAdaptive) init(concurrency uint, stateSizeBudget *int) {
hma.reset()
hma.stateSizeBudget = stateSizeBudget
hma.concurrency = concurrency
}
func (hma *hitsMapAdaptive) clear() {
*hma.stateSizeBudget += hma.stateSize()
hma.init(hma.concurrency, hma.stateSizeBudget)
}
func (hma *hitsMapAdaptive) stateSize() int {
n := hma.hm.stateSize()
for i := range hma.shards {
n += hma.shards[i].stateSize()
}
return n
}
func (hma *hitsMapAdaptive) entriesCount() uint64 {
if hma.shards == nil {
return hma.hm.entriesCount()
}
shards := hma.shards
n := uint64(0)
for i := range shards {
n += shards[i].entriesCount()
}
return n
}
func (hma *hitsMapAdaptive) updateStateGeneric(key string, hits uint64) {
if n, ok := tryParseUint64(key); ok {
hma.updateStateUint64(n, hits)
return
}
if len(key) > 0 && key[0] == '-' {
if n, ok := tryParseInt64(key); ok {
hma.updateStateNegativeInt64(n, hits)
return
}
}
hma.updateStateString(bytesutil.ToUnsafeBytes(key), hits)
}
func (hma *hitsMapAdaptive) updateStateInt64(n int64, hits uint64) {
if n >= 0 {
hma.updateStateUint64(uint64(n), hits)
} else {
hma.updateStateNegativeInt64(n, hits)
}
}
func (hma *hitsMapAdaptive) updateStateUint64(n, hits uint64) {
if hma.shards == nil {
stateSize := hma.hm.updateStateUint64(&hma.a, n, hits)
if stateSize > 0 {
*hma.stateSizeBudget -= stateSize
hma.probablyMoveToShards()
}
return
}
hm := hma.getShardByUint64(n)
*hma.stateSizeBudget -= hm.updateStateUint64(&hma.a, n, hits)
}
func (hma *hitsMapAdaptive) updateStateNegativeInt64(n int64, hits uint64) {
if hma.shards == nil {
stateSize := hma.hm.updateStateNegativeInt64(&hma.a, n, hits)
if stateSize > 0 {
*hma.stateSizeBudget -= stateSize
hma.probablyMoveToShards()
}
return
}
hm := hma.getShardByUint64(uint64(n))
*hma.stateSizeBudget -= hm.updateStateNegativeInt64(&hma.a, n, hits)
}
func (hma *hitsMapAdaptive) updateStateString(key []byte, hits uint64) {
if hma.shards == nil {
stateSize := hma.hm.updateStateString(&hma.a, key, hits)
if stateSize > 0 {
*hma.stateSizeBudget -= stateSize
hma.probablyMoveToShards()
}
return
}
hm := hma.getShardByString(key)
*hma.stateSizeBudget -= hm.updateStateString(&hma.a, key, hits)
}
func (hma *hitsMapAdaptive) probablyMoveToShards() {
if hma.hm.entriesCount() < hitsMapAdaptiveMaxLen {
return
}
hma.moveToShards()
}
func (hma *hitsMapAdaptive) moveToShards() {
hma.shards = hma.a.newHitsMaps(hma.concurrency)
for n, pHits := range hma.hm.u64 {
hm := hma.getShardByUint64(n)
hm.setStateUint64(n, pHits)
}
for n, pHits := range hma.hm.negative64 {
hm := hma.getShardByUint64(n)
hm.setStateNegativeInt64(int64(n), pHits)
}
for s, pHits := range hma.hm.strings {
hm := hma.getShardByString(bytesutil.ToUnsafeBytes(s))
hm.setStateString(s, pHits)
}
hma.hm.reset()
}
func (hma *hitsMapAdaptive) getShardByUint64(n uint64) *hitsMap {
h := fastHashUint64(n)
shardIdx := h % uint64(len(hma.shards))
return &hma.shards[shardIdx]
}
func (hma *hitsMapAdaptive) getShardByString(v []byte) *hitsMap {
h := xxhash.Sum64(v)
shardIdx := h % uint64(len(hma.shards))
return &hma.shards[shardIdx]
}
type hitsMap struct {
u64 map[uint64]*uint64
negative64 map[uint64]*uint64
strings map[string]*uint64
}
func (hm *hitsMap) reset() {
*hm = hitsMap{}
hm.stateSizeBudget = nil
hm.u64 = nil
hm.negative64 = nil
hm.strings = nil
}
func (hm *hitsMap) clear() {
*hm.stateSizeBudget += hm.stateSize()
hm.init(hm.stateSizeBudget)
}
func (hm *hitsMap) init(stateSizeBudget *int) {
hm.stateSizeBudget = stateSizeBudget
hm.u64 = make(map[uint64]*uint64)
hm.negative64 = make(map[uint64]*uint64)
hm.strings = make(map[string]*uint64)
}
func (hm *hitsMap) entriesCount() uint64 {
@@ -187,89 +47,76 @@ func (hm *hitsMap) entriesCount() uint64 {
}
func (hm *hitsMap) stateSize() int {
size := 0
for n, pHits := range hm.u64 {
size += int(unsafe.Sizeof(n) + unsafe.Sizeof(pHits) + unsafe.Sizeof(*pHits))
n := 24*(len(hm.u64)+len(hm.negative64)) + 40*len(hm.strings)
for k := range hm.strings {
n += len(k)
}
for n, pHits := range hm.negative64 {
size += int(unsafe.Sizeof(n) + unsafe.Sizeof(pHits) + unsafe.Sizeof(*pHits))
}
for k, pHits := range hm.strings {
size += len(k) + int(unsafe.Sizeof(k)+unsafe.Sizeof(pHits)+unsafe.Sizeof(*pHits))
}
return size
return n
}
func (hm *hitsMap) updateStateUint64(a *chunkedAllocator, n, hits uint64) int {
func (hm *hitsMap) updateStateGeneric(key string, hits uint64) {
if n, ok := tryParseUint64(key); ok {
hm.updateStateUint64(n, hits)
return
}
if len(key) > 0 && key[0] == '-' {
if n, ok := tryParseInt64(key); ok {
hm.updateStateNegativeInt64(n, hits)
return
}
}
hm.updateStateString(bytesutil.ToUnsafeBytes(key), hits)
}
func (hm *hitsMap) updateStateInt64(n int64, hits uint64) {
if n >= 0 {
hm.updateStateUint64(uint64(n), hits)
} else {
hm.updateStateNegativeInt64(n, hits)
}
}
func (hm *hitsMap) updateStateUint64(n, hits uint64) {
pHits := hm.u64[n]
if pHits != nil {
*pHits += hits
return 0
return
}
pHits = a.newUint64()
pHits = hm.a.newUint64()
*pHits = hits
return int(unsafe.Sizeof(*pHits)) + hm.setStateUint64(n, pHits)
}
func (hm *hitsMap) setStateUint64(n uint64, pHits *uint64) int {
if hm.u64 == nil {
hm.u64 = map[uint64]*uint64{
n: pHits,
}
return int(unsafe.Sizeof(hm.u64) + unsafe.Sizeof(n) + unsafe.Sizeof(pHits))
}
hm.u64[n] = pHits
return int(unsafe.Sizeof(n) + unsafe.Sizeof(pHits))
*hm.stateSizeBudget -= 24
}
func (hm *hitsMap) updateStateNegativeInt64(a *chunkedAllocator, n int64, hits uint64) int {
func (hm *hitsMap) updateStateNegativeInt64(n int64, hits uint64) {
pHits := hm.negative64[uint64(n)]
if pHits != nil {
*pHits += hits
return 0
return
}
pHits = a.newUint64()
pHits = hm.a.newUint64()
*pHits = hits
return int(unsafe.Sizeof(*pHits)) + hm.setStateNegativeInt64(n, pHits)
}
func (hm *hitsMap) setStateNegativeInt64(n int64, pHits *uint64) int {
if hm.negative64 == nil {
hm.negative64 = map[uint64]*uint64{
uint64(n): pHits,
}
return int(unsafe.Sizeof(hm.negative64) + unsafe.Sizeof(uint64(n)) + unsafe.Sizeof(pHits))
}
hm.negative64[uint64(n)] = pHits
return int(unsafe.Sizeof(n) + unsafe.Sizeof(pHits))
*hm.stateSizeBudget -= 24
}
func (hm *hitsMap) updateStateString(a *chunkedAllocator, key []byte, hits uint64) int {
func (hm *hitsMap) updateStateString(key []byte, hits uint64) {
pHits := hm.strings[string(key)]
if pHits != nil {
*pHits += hits
return 0
return
}
keyCopy := a.cloneBytesToString(key)
pHits = a.newUint64()
keyCopy := hm.a.cloneBytesToString(key)
pHits = hm.a.newUint64()
*pHits = hits
return len(keyCopy) + int(unsafe.Sizeof(*pHits)) + hm.setStateString(keyCopy, pHits)
}
hm.strings[keyCopy] = pHits
func (hm *hitsMap) setStateString(v string, pHits *uint64) int {
if hm.strings == nil {
hm.strings = map[string]*uint64{
v: pHits,
}
return int(unsafe.Sizeof(hm.strings) + unsafe.Sizeof(v) + unsafe.Sizeof(pHits))
}
hm.strings[v] = pHits
return int(unsafe.Sizeof(v) + unsafe.Sizeof(pHits))
*hm.stateSizeBudget -= len(keyCopy) + 40
}
func (hm *hitsMap) mergeState(src *hitsMap, stopCh <-chan struct{}) {
@@ -279,7 +126,7 @@ func (hm *hitsMap) mergeState(src *hitsMap, stopCh <-chan struct{}) {
}
pHitsDst := hm.u64[n]
if pHitsDst == nil {
hm.setStateUint64(n, pHitsSrc)
hm.u64[n] = pHitsSrc
} else {
*pHitsDst += *pHitsSrc
}
@@ -290,7 +137,7 @@ func (hm *hitsMap) mergeState(src *hitsMap, stopCh <-chan struct{}) {
}
pHitsDst := hm.negative64[n]
if pHitsDst == nil {
hm.setStateNegativeInt64(int64(n), pHitsSrc)
hm.negative64[n] = pHitsSrc
} else {
*pHitsDst += *pHitsSrc
}
@@ -301,52 +148,89 @@ func (hm *hitsMap) mergeState(src *hitsMap, stopCh <-chan struct{}) {
}
pHitsDst := hm.strings[k]
if pHitsDst == nil {
hm.setStateString(k, pHitsSrc)
hm.strings[k] = pHitsSrc
} else {
*pHitsDst += *pHitsSrc
}
}
}
// hitsMapMergeParallel merges hmas in parallel
// hitsMapMergeParallel merges hms in parallel on the given cpusCount
//
// The merged disjoint parts of hmas are passed to f.
// The mered disjoint parts of hms are passed to f.
// The function may be interrupted by closing stopCh.
// The caller must check for closed stopCh after returning from the function.
func hitsMapMergeParallel(hmas []*hitsMapAdaptive, stopCh <-chan struct{}, f func(hm *hitsMap)) {
if len(hmas) == 0 {
func hitsMapMergeParallel(hms []*hitsMap, cpusCount int, stopCh <-chan struct{}, f func(hm *hitsMap)) {
srcLen := len(hms)
if srcLen < 2 {
// Nothing to merge
if len(hms) == 1 {
f(hms[0])
}
return
}
var wg sync.WaitGroup
for i := range hmas {
hma := hmas[i]
if hma.shards != nil {
continue
}
perShardMaps := make([][]hitsMap, srcLen)
for i := range hms {
wg.Add(1)
go func() {
go func(idx int) {
defer wg.Done()
hma.moveToShards()
}()
stateSizeBudget := 0
perCPU := make([]hitsMap, cpusCount)
for i := range perCPU {
perCPU[i].init(&stateSizeBudget)
}
hm := hms[idx]
for n, pHits := range hm.u64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].u64[n] = pHits
}
for n, pHits := range hm.negative64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].negative64[n] = pHits
}
for k, pHits := range hm.strings {
if needStop(stopCh) {
return
}
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(k))
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].strings[k] = pHits
}
perShardMaps[idx] = perCPU
hm.reset()
}(i)
}
wg.Wait()
if needStop(stopCh) {
return
}
cpusCount := len(hmas[0].shards)
// Merge per-shard entries into perShardMaps[0]
for i := 0; i < cpusCount; i++ {
wg.Add(1)
go func(cpuIdx int) {
defer wg.Done()
hm := &hmas[0].shards[cpuIdx]
for j := range hmas[1:] {
src := &hmas[1+j].shards[cpuIdx]
hm.mergeState(src, stopCh)
src.reset()
hm := &perShardMaps[0][cpuIdx]
for _, perCPU := range perShardMaps[1:] {
hm.mergeState(&perCPU[cpuIdx], stopCh)
perCPU[cpuIdx].reset()
}
f(hm)
}(i)

View File

@@ -3,7 +3,6 @@ package logstorage
import (
"fmt"
"sort"
"sync"
"sync/atomic"
"unsafe"
@@ -79,25 +78,25 @@ func (pf *pipeFacets) visitSubqueries(_ func(q *Query)) {
func (pf *pipeFacets) newPipeProcessor(workersCount int, stopCh <-chan struct{}, cancel func(), ppNext pipeProcessor) pipeProcessor {
maxStateSize := int64(float64(memory.Allowed()) * 0.2)
shards := make([]pipeFacetsProcessorShard, workersCount)
for i := range shards {
shards[i] = pipeFacetsProcessorShard{
pipeFacetsProcessorShardNopad: pipeFacetsProcessorShardNopad{
pf: pf,
},
}
}
pfp := &pipeFacetsProcessor{
pf: pf,
stopCh: stopCh,
cancel: cancel,
ppNext: ppNext,
shards: shards,
maxStateSize: maxStateSize,
}
shards := make([]pipeFacetsProcessorShard, workersCount)
for i := range shards {
shards[i] = pipeFacetsProcessorShard{
pipeFacetsProcessorShardNopad: pipeFacetsProcessorShardNopad{
pfp: pfp,
},
}
}
pfp.shards = shards
pfp.stateSizeBudget.Store(maxStateSize)
return pfp
@@ -123,8 +122,8 @@ type pipeFacetsProcessorShard struct {
}
type pipeFacetsProcessorShardNopad struct {
// pfp points to the parent pipeFacetsProcessor.
pfp *pipeFacetsProcessor
// pf points to the parent pipeFacets.
pf *pipeFacets
// a is used for reducing memory allocations when counting facets over big number of unique fields
a chunkedAllocator
@@ -141,7 +140,7 @@ type pipeFacetsProcessorShardNopad struct {
}
type pipeFacetsFieldHits struct {
m hitsMapAdaptive
m hitsMap
mustIgnore bool
}
@@ -164,7 +163,7 @@ func (shard *pipeFacetsProcessorShard) updateFacetsForColumn(br *blockResult, c
if fhs.mustIgnore {
return
}
if fhs.m.entriesCount() >= shard.pfp.pf.maxValuesPerField {
if fhs.m.entriesCount() >= shard.pf.maxValuesPerField {
// Ignore fields with too many unique values
fhs.enableIgnoreField()
return
@@ -220,7 +219,7 @@ func (shard *pipeFacetsProcessorShard) updateFacetsForColumn(br *blockResult, c
}
func (shard *pipeFacetsProcessorShard) updateStateInt64(fhs *pipeFacetsFieldHits, n int64) {
if maxValueLen := shard.pfp.pf.maxValueLen; maxValueLen <= 21 && uint64(int64StringLen(n)) > maxValueLen {
if maxValueLen := shard.pf.maxValueLen; maxValueLen <= 21 && uint64(int64StringLen(n)) > maxValueLen {
// Ignore fields with too long values, since they are hard to use in faceted search.
fhs.enableIgnoreField()
return
@@ -229,7 +228,7 @@ func (shard *pipeFacetsProcessorShard) updateStateInt64(fhs *pipeFacetsFieldHits
}
func (shard *pipeFacetsProcessorShard) updateStateUint64(fhs *pipeFacetsFieldHits, n uint64) {
if maxValueLen := shard.pfp.pf.maxValueLen; maxValueLen <= 20 && uint64(uint64StringLen(n)) > maxValueLen {
if maxValueLen := shard.pf.maxValueLen; maxValueLen <= 20 && uint64(uint64StringLen(n)) > maxValueLen {
// Ignore fields with too long values, since they are hard to use in faceted search.
fhs.enableIgnoreField()
return
@@ -289,7 +288,7 @@ func (shard *pipeFacetsProcessorShard) updateStateGeneric(fhs *pipeFacetsFieldHi
// So it is better ignoring empty values.
return
}
if uint64(len(v)) > shard.pfp.pf.maxValueLen {
if uint64(len(v)) > shard.pf.maxValueLen {
// Ignore fields with too long values, since they are hard to use in faceted search.
fhs.enableIgnoreField()
return
@@ -304,7 +303,7 @@ func (shard *pipeFacetsProcessorShard) getFieldHits(fieldName string) *pipeFacet
fhs, ok := shard.m[fieldName]
if !ok {
fhs = &pipeFacetsFieldHits{}
fhs.m.init(uint(len(shard.pfp.shards)), &shard.stateSizeBudget)
fhs.m.init(&shard.stateSizeBudget)
fieldNameCopy := shard.a.cloneString(fieldName)
shard.m[fieldNameCopy] = fhs
shard.stateSizeBudget -= len(fieldNameCopy) + int(unsafe.Sizeof(fhs)+unsafe.Sizeof(*fhs))
@@ -342,7 +341,7 @@ func (pfp *pipeFacetsProcessor) flush() error {
}
// merge state across shards
hmasByFieldName := make(map[string][]*hitsMapAdaptive)
hms := make(map[string]*hitsMap)
rowsTotal := uint64(0)
for _, shard := range pfp.shards {
if needStop(pfp.stopCh) {
@@ -352,14 +351,19 @@ func (pfp *pipeFacetsProcessor) flush() error {
if fhs.mustIgnore {
continue
}
hmasByFieldName[fieldName] = append(hmasByFieldName[fieldName], &fhs.m)
hm, ok := hms[fieldName]
if !ok {
hms[fieldName] = &fhs.m
continue
}
hm.mergeState(&fhs.m, pfp.stopCh)
}
rowsTotal += shard.rowsTotal
}
// sort fieldNames
fieldNames := make([]string, 0, len(hmasByFieldName))
for fieldName := range hmasByFieldName {
fieldNames := make([]string, 0, len(hms))
for fieldName := range hms {
fieldNames = append(fieldNames, fieldName)
}
sort.Strings(fieldNames)
@@ -373,30 +377,31 @@ func (pfp *pipeFacetsProcessor) flush() error {
if needStop(pfp.stopCh) {
return nil
}
hmas := hmasByFieldName[fieldName]
var hms []*hitsMap
var hmsLock sync.Mutex
hitsMapMergeParallel(hmas, pfp.stopCh, func(hm *hitsMap) {
hmsLock.Lock()
hms = append(hms, hm)
hmsLock.Unlock()
})
entriesCount := uint64(0)
for _, hm := range hms {
entriesCount += hm.entriesCount()
}
if entriesCount > pfp.pf.maxValuesPerField {
hm := hms[fieldName]
if hm.entriesCount() > pfp.pf.maxValuesPerField {
continue
}
vs := make([]pipeTopEntry, 0, entriesCount)
for _, hm := range hms {
vs = appendTopEntryFacets(vs, hm)
vs := make([]pipeTopEntry, 0, hm.entriesCount())
for n, pHits := range hm.u64 {
vs = append(vs, pipeTopEntry{
k: string(marshalUint64String(nil, n)),
hits: *pHits,
})
}
if len(vs) == 1 && vs[0].hits == rowsTotal && !wctx.pfp.pf.keepConstFields {
for n, pHits := range hm.negative64 {
vs = append(vs, pipeTopEntry{
k: string(marshalInt64String(nil, int64(n))),
hits: *pHits,
})
}
for k, pHits := range hm.strings {
vs = append(vs, pipeTopEntry{
k: k,
hits: *pHits,
})
}
if len(vs) == 1 && vs[0].hits == rowsTotal && !pfp.pf.keepConstFields {
// Skip field with constant value.
continue
}
@@ -407,9 +412,6 @@ func (pfp *pipeFacetsProcessor) flush() error {
vs = vs[:limit]
}
for _, v := range vs {
if needStop(pfp.stopCh) {
return nil
}
wctx.writeRow(fieldName, v.k, v.hits)
}
}
@@ -418,28 +420,6 @@ func (pfp *pipeFacetsProcessor) flush() error {
return nil
}
func appendTopEntryFacets(dst []pipeTopEntry, hm *hitsMap) []pipeTopEntry {
for n, pHits := range hm.u64 {
dst = append(dst, pipeTopEntry{
k: string(marshalUint64String(nil, n)),
hits: *pHits,
})
}
for n, pHits := range hm.negative64 {
dst = append(dst, pipeTopEntry{
k: string(marshalInt64String(nil, int64(n))),
hits: *pHits,
})
}
for k, pHits := range hm.strings {
dst = append(dst, pipeTopEntry{
k: k,
hits: *pHits,
})
}
return dst
}
type pipeFacetsWriteContext struct {
pfp *pipeFacetsProcessor
rcs []resultColumn

View File

@@ -13,6 +13,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
)
// pipeStats processes '| stats ...' queries.
@@ -45,8 +46,6 @@ type statsFunc interface {
updateNeededFields(neededFields fieldsSet)
// newStatsProcessor must create new statsProcessor for calculating stats for the given statsFunc
//
// a must be used for allocating memory inside the returned statsProcessor.
newStatsProcessor(a *chunkedAllocator) statsProcessor
}
@@ -211,26 +210,26 @@ const stateSizeBudgetChunk = 1 << 20
func (ps *pipeStats) newPipeProcessor(workersCount int, stopCh <-chan struct{}, cancel func(), ppNext pipeProcessor) pipeProcessor {
maxStateSize := int64(float64(memory.Allowed()) * 0.4)
shards := make([]pipeStatsProcessorShard, workersCount)
for i := range shards {
shards[i] = pipeStatsProcessorShard{
pipeStatsProcessorShardNopad: pipeStatsProcessorShardNopad{
ps: ps,
},
}
shards[i].init()
}
psp := &pipeStatsProcessor{
ps: ps,
stopCh: stopCh,
cancel: cancel,
ppNext: ppNext,
shards: shards,
maxStateSize: maxStateSize,
}
shards := make([]pipeStatsProcessorShard, workersCount)
for i := range shards {
shards[i] = pipeStatsProcessorShard{
pipeStatsProcessorShardNopad: pipeStatsProcessorShardNopad{
psp: psp,
},
}
shards[i].init()
}
psp.shards = shards
psp.stateSizeBudget.Store(maxStateSize)
return psp
@@ -256,19 +255,9 @@ type pipeStatsProcessorShard struct {
}
type pipeStatsProcessorShardNopad struct {
psp *pipeStatsProcessor
ps *pipeStats
// groupMap is used for tracking small number of groups until it reaches pipeStatsGroupMapMaxLen.
// After that the groups are tracked by groupMapShards.
groupMap pipeStatsGroupMap
// groupMapShards are used for tracking big number of groups.
//
// Every shard contains a share of unique groups, which are merged in parallel at flush().
groupMapShards []pipeStatsGroupMap
// a is used for reducing memory allocations when calculating stats among big number of different groups.
a chunkedAllocator
m pipeStatsGroupMap
// bms and brTmp are used for applying per-func filters.
bms []bitmap
@@ -280,26 +269,34 @@ type pipeStatsProcessorShardNopad struct {
stateSizeBudget int
}
// the maximum number of groups to track in pipeStatsProcessorShard.groupMap before switching to pipeStatsProcessorShard.groupMapShards
//
// Too big value may slow down flush() across big number of CPU cores.
// Too small value may significantly increase RAM usage when stats for big number of groups is calculated.
const pipeStatsGroupMapMaxLen = 4 << 10
type pipeStatsGroupMap struct {
shard *pipeStatsProcessorShard
u64 map[uint64]*pipeStatsGroup
negative64 map[uint64]*pipeStatsGroup
strings map[string]*pipeStatsGroup
// a and sfpsBuf are used for reducing memory allocations when calculating stats among big number of different groups.
a chunkedAllocator
sfpsBuf []statsProcessor
}
func (psm *pipeStatsGroupMap) reset() {
*psm = pipeStatsGroupMap{}
psm.shard = nil
psm.u64 = nil
psm.negative64 = nil
psm.strings = nil
psm.sfpsBuf = nil
}
func (psm *pipeStatsGroupMap) init(shard *pipeStatsProcessorShard) {
psm.shard = shard
psm.u64 = make(map[uint64]*pipeStatsGroup)
psm.negative64 = make(map[uint64]*pipeStatsGroup)
psm.strings = make(map[string]*pipeStatsGroup)
}
func (psm *pipeStatsGroupMap) entriesCount() uint64 {
@@ -307,74 +304,98 @@ func (psm *pipeStatsGroupMap) entriesCount() uint64 {
return uint64(n)
}
func (psm *pipeStatsGroupMap) getPipeStatsGroupUint64(n uint64) (*pipeStatsGroup, bool) {
if psg := psm.u64[n]; psg != nil {
return psg, false
func (psm *pipeStatsGroupMap) getPipeStatsGroupGeneric(key string) *pipeStatsGroup {
if n, ok := tryParseUint64(key); ok {
return psm.getPipeStatsGroupUint64(n)
}
psg := psm.shard.newPipeStatsGroup()
psm.setPipeStatsGroupUint64(n, psg)
return psg, true
}
func (psm *pipeStatsGroupMap) setPipeStatsGroupUint64(n uint64, psg *pipeStatsGroup) {
if psm.u64 == nil {
psm.u64 = map[uint64]*pipeStatsGroup{
n: psg,
if len(key) > 0 && key[0] == '-' {
if n, ok := tryParseInt64(key); ok {
return psm.getPipeStatsGroupNegativeInt64(n)
}
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(psm.u64) + unsafe.Sizeof(n) + unsafe.Sizeof(psg))
} else {
psm.u64[n] = psg
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(n) + unsafe.Sizeof(psg))
}
return psm.getPipeStatsGroupString(bytesutil.ToUnsafeBytes(key))
}
func (psm *pipeStatsGroupMap) getPipeStatsGroupNegativeInt64(n int64) (*pipeStatsGroup, bool) {
if psg := psm.negative64[uint64(n)]; psg != nil {
return psg, false
func (psm *pipeStatsGroupMap) getPipeStatsGroupInt64(n int64) *pipeStatsGroup {
if n >= 0 {
return psm.getPipeStatsGroupUint64(uint64(n))
}
psg := psm.shard.newPipeStatsGroup()
psm.setPipeStatsGroupNegativeInt64(n, psg)
return psg, true
return psm.getPipeStatsGroupNegativeInt64(n)
}
func (psm *pipeStatsGroupMap) setPipeStatsGroupNegativeInt64(n int64, psg *pipeStatsGroup) {
if psm.negative64 == nil {
psm.negative64 = map[uint64]*pipeStatsGroup{
uint64(n): psg,
}
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(psm.negative64) + unsafe.Sizeof(n) + unsafe.Sizeof(psg))
} else {
psm.negative64[uint64(n)] = psg
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(n) + unsafe.Sizeof(psg))
}
}
func (psm *pipeStatsGroupMap) getPipeStatsGroupString(key []byte) (*pipeStatsGroup, bool) {
if psg := psm.strings[string(key)]; psg != nil {
return psg, false
func (psm *pipeStatsGroupMap) getPipeStatsGroupUint64(n uint64) *pipeStatsGroup {
psg := psm.u64[n]
if psg != nil {
return psg
}
psg := psm.shard.newPipeStatsGroup()
keyCopy := psm.shard.a.cloneBytesToString(key)
psm.shard.stateSizeBudget -= len(keyCopy)
psm.setPipeStatsGroupString(keyCopy, psg)
return psg, true
psg = psm.newPipeStatsGroup()
psm.u64[n] = psg
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(n) + unsafe.Sizeof(psg))
return psg
}
func (psm *pipeStatsGroupMap) setPipeStatsGroupString(v string, psg *pipeStatsGroup) {
if psm.strings == nil {
psm.strings = map[string]*pipeStatsGroup{
v: psg,
}
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(psm.strings) + unsafe.Sizeof(v))
} else {
psm.strings[v] = psg
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(v))
func (psm *pipeStatsGroupMap) getPipeStatsGroupNegativeInt64(n int64) *pipeStatsGroup {
psg := psm.negative64[uint64(n)]
if psg != nil {
return psg
}
psg = psm.newPipeStatsGroup()
psm.negative64[uint64(n)] = psg
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(n) + unsafe.Sizeof(psg))
return psg
}
func (psm *pipeStatsGroupMap) getPipeStatsGroupString(key []byte) *pipeStatsGroup {
psg := psm.strings[string(key)]
if psg != nil {
return psg
}
psg = psm.newPipeStatsGroup()
keyCopy := psm.a.cloneBytesToString(key)
psm.strings[keyCopy] = psg
psm.shard.stateSizeBudget -= len(keyCopy) + int(unsafe.Sizeof(keyCopy))
return psg
}
func (psm *pipeStatsGroupMap) newPipeStatsGroup() *pipeStatsGroup {
sfps := psm.newStatsProcessors()
for i, f := range psm.shard.ps.funcs {
bytesAllocated := psm.a.bytesAllocated
sfps[i] = f.f.newStatsProcessor(&psm.a)
psm.shard.stateSizeBudget -= psm.a.bytesAllocated - bytesAllocated
}
psg := psm.a.newPipeStatsGroup()
psg.funcs = psm.shard.ps.funcs
psg.sfps = sfps
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(*psg) + unsafe.Sizeof(sfps[0])*uintptr(len(sfps)))
return psg
}
func (psm *pipeStatsGroupMap) newStatsProcessors() []statsProcessor {
funcsLen := len(psm.shard.ps.funcs)
if len(psm.sfpsBuf)+funcsLen > cap(psm.sfpsBuf) {
psm.sfpsBuf = nil
}
if psm.sfpsBuf == nil {
psm.sfpsBuf = make([]statsProcessor, 0, pipeStatsProcessorChunkLen)
}
sfpsBufLen := len(psm.sfpsBuf)
psm.sfpsBuf = slicesutil.SetLength(psm.sfpsBuf, sfpsBufLen+funcsLen)
return psm.sfpsBuf[sfpsBufLen:]
}
const pipeStatsProcessorChunkLen = 64 * 1024 / int(unsafe.Sizeof((statsProcessor)(nil)))
func (psm *pipeStatsGroupMap) mergeState(src *pipeStatsGroupMap, stopCh <-chan struct{}) {
for n, psgSrc := range src.u64 {
if needStop(stopCh) {
@@ -382,7 +403,7 @@ func (psm *pipeStatsGroupMap) mergeState(src *pipeStatsGroupMap, stopCh <-chan s
}
psgDst := psm.u64[n]
if psgDst == nil {
psm.setPipeStatsGroupUint64(n, psgSrc)
psm.u64[n] = psgSrc
} else {
psgDst.mergeState(psgSrc)
}
@@ -393,7 +414,7 @@ func (psm *pipeStatsGroupMap) mergeState(src *pipeStatsGroupMap, stopCh <-chan s
}
psgDst := psm.negative64[n]
if psgDst == nil {
psm.setPipeStatsGroupNegativeInt64(int64(n), psgSrc)
psm.negative64[n] = psgSrc
} else {
psgDst.mergeState(psgSrc)
}
@@ -404,54 +425,22 @@ func (psm *pipeStatsGroupMap) mergeState(src *pipeStatsGroupMap, stopCh <-chan s
}
psgDst := psm.strings[k]
if psgDst == nil {
psm.setPipeStatsGroupString(k, psgSrc)
psm.strings[k] = psgSrc
} else {
psgDst.mergeState(psgSrc)
}
}
}
func initStatsConcurrency(sfp statsProcessor, concurrency uint) {
switch t := sfp.(type) {
case *statsCountUniqProcessor:
t.concurrency = concurrency
case *statsCountUniqHashProcessor:
t.concurrency = concurrency
case *statsUniqValuesProcessor:
t.concurrency = concurrency
}
}
func (shard *pipeStatsProcessorShard) init() {
shard.groupMap.init(shard)
shard.m.init(shard)
funcsLen := len(shard.psp.ps.funcs)
funcsLen := len(shard.ps.funcs)
shard.bms = make([]bitmap, funcsLen)
}
func (shard *pipeStatsProcessorShard) newPipeStatsGroup() *pipeStatsGroup {
bytesAllocated := shard.a.bytesAllocated
funcsLen := len(shard.psp.ps.funcs)
sfps := shard.a.newStatsProcessors(uint(funcsLen))
for i, f := range shard.psp.ps.funcs {
sfp := f.f.newStatsProcessor(&shard.a)
initStatsConcurrency(sfp, uint(len(shard.psp.shards)))
sfps[i] = sfp
}
psg := shard.a.newPipeStatsGroup()
psg.funcs = shard.psp.ps.funcs
psg.sfps = sfps
shard.stateSizeBudget -= shard.a.bytesAllocated - bytesAllocated
return psg
}
func (shard *pipeStatsProcessorShard) writeBlock(br *blockResult) {
byFields := shard.psp.ps.byFields
byFields := shard.ps.byFields
// Update shard.bms by applying per-function filters
shard.applyPerFunctionFilters(br)
@@ -459,7 +448,7 @@ func (shard *pipeStatsProcessorShard) writeBlock(br *blockResult) {
// Process stats for the defined functions
if len(byFields) == 0 {
// Fast path - pass all the rows to a single group with empty key.
psg := shard.getPipeStatsGroupString(nil)
psg := shard.m.getPipeStatsGroupString(nil)
shard.stateSizeBudget -= psg.updateStatsForAllRows(shard.bms, br, &shard.brTmp)
return
}
@@ -492,7 +481,7 @@ func (shard *pipeStatsProcessorShard) writeBlock(br *blockResult) {
for _, values := range columnValues {
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(values[0]))
}
psg := shard.getPipeStatsGroupString(keyBuf)
psg := shard.m.getPipeStatsGroupString(keyBuf)
shard.stateSizeBudget -= psg.updateStatsForAllRows(shard.bms, br, &shard.brTmp)
shard.keyBuf = keyBuf
return
@@ -516,7 +505,7 @@ func (shard *pipeStatsProcessorShard) writeBlock(br *blockResult) {
for _, values := range columnValues {
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(values[i]))
}
psg = shard.getPipeStatsGroupString(keyBuf)
psg = shard.m.getPipeStatsGroupString(keyBuf)
}
shard.stateSizeBudget -= psg.updateStatsForRow(shard.bms, br, i)
}
@@ -528,7 +517,7 @@ func (shard *pipeStatsProcessorShard) updateStatsSingleColumn(br *blockResult, b
if c.isConst {
// Fast path for column with a constant value.
v := br.getBucketedValue(c.valuesEncoded[0], bf)
psg := shard.getPipeStatsGroupGeneric(v)
psg := shard.m.getPipeStatsGroupGeneric(v)
shard.stateSizeBudget -= psg.updateStatsForAllRows(shard.bms, br, &shard.brTmp)
return
}
@@ -541,7 +530,7 @@ func (shard *pipeStatsProcessorShard) updateStatsSingleColumn(br *blockResult, b
for i, v := range values {
if i <= 0 || values[i-1] != v {
n := unmarshalUint8(v)
psg = shard.getPipeStatsGroupUint64(uint64(n))
psg = shard.m.getPipeStatsGroupUint64(uint64(n))
}
shard.stateSizeBudget -= psg.updateStatsForRow(shard.bms, br, i)
}
@@ -552,7 +541,7 @@ func (shard *pipeStatsProcessorShard) updateStatsSingleColumn(br *blockResult, b
for i, v := range values {
if i <= 0 || values[i-1] != v {
n := unmarshalUint16(v)
psg = shard.getPipeStatsGroupUint64(uint64(n))
psg = shard.m.getPipeStatsGroupUint64(uint64(n))
}
shard.stateSizeBudget -= psg.updateStatsForRow(shard.bms, br, i)
}
@@ -563,7 +552,7 @@ func (shard *pipeStatsProcessorShard) updateStatsSingleColumn(br *blockResult, b
for i, v := range values {
if i <= 0 || values[i-1] != v {
n := unmarshalUint32(v)
psg = shard.getPipeStatsGroupUint64(uint64(n))
psg = shard.m.getPipeStatsGroupUint64(uint64(n))
}
shard.stateSizeBudget -= psg.updateStatsForRow(shard.bms, br, i)
}
@@ -574,7 +563,7 @@ func (shard *pipeStatsProcessorShard) updateStatsSingleColumn(br *blockResult, b
for i, v := range values {
if i <= 0 || values[i-1] != v {
n := unmarshalUint64(v)
psg = shard.getPipeStatsGroupUint64(n)
psg = shard.m.getPipeStatsGroupUint64(n)
}
shard.stateSizeBudget -= psg.updateStatsForRow(shard.bms, br, i)
}
@@ -585,7 +574,7 @@ func (shard *pipeStatsProcessorShard) updateStatsSingleColumn(br *blockResult, b
for i, v := range values {
if i <= 0 || values[i-1] != v {
n := unmarshalInt64(v)
psg = shard.getPipeStatsGroupInt64(n)
psg = shard.m.getPipeStatsGroupInt64(n)
}
shard.stateSizeBudget -= psg.updateStatsForRow(shard.bms, br, i)
}
@@ -598,14 +587,14 @@ func (shard *pipeStatsProcessorShard) updateStatsSingleColumn(br *blockResult, b
values := c.getValuesBucketed(br, bf)
for i := 0; i < br.rowsLen; i++ {
if i <= 0 || values[i-1] != values[i] {
psg = shard.getPipeStatsGroupGeneric(values[i])
psg = shard.m.getPipeStatsGroupGeneric(values[i])
}
shard.stateSizeBudget -= psg.updateStatsForRow(shard.bms, br, i)
}
}
func (shard *pipeStatsProcessorShard) applyPerFunctionFilters(br *blockResult) {
funcs := shard.psp.ps.funcs
funcs := shard.ps.funcs
for i := range funcs {
iff := funcs[i].iff
if iff == nil {
@@ -620,111 +609,6 @@ func (shard *pipeStatsProcessorShard) applyPerFunctionFilters(br *blockResult) {
}
}
func (shard *pipeStatsProcessorShard) getPipeStatsGroupGeneric(v string) *pipeStatsGroup {
if n, ok := tryParseUint64(v); ok {
return shard.getPipeStatsGroupUint64(n)
}
if len(v) > 0 && v[0] == '-' {
if n, ok := tryParseInt64(v); ok {
return shard.getPipeStatsGroupNegativeInt64(n)
}
}
return shard.getPipeStatsGroupString(bytesutil.ToUnsafeBytes(v))
}
func (shard *pipeStatsProcessorShard) getPipeStatsGroupInt64(n int64) *pipeStatsGroup {
if n >= 0 {
return shard.getPipeStatsGroupUint64(uint64(n))
}
return shard.getPipeStatsGroupNegativeInt64(n)
}
func (shard *pipeStatsProcessorShard) getPipeStatsGroupUint64(n uint64) *pipeStatsGroup {
if shard.groupMapShards == nil {
psg, isNew := shard.groupMap.getPipeStatsGroupUint64(n)
if isNew {
shard.probablyMoveGroupMapToShards()
}
return psg
}
psm := shard.getGroupMapShardByUint64(n)
psg, _ := psm.getPipeStatsGroupUint64(n)
return psg
}
func (shard *pipeStatsProcessorShard) getPipeStatsGroupNegativeInt64(n int64) *pipeStatsGroup {
if shard.groupMapShards == nil {
psg, isNew := shard.groupMap.getPipeStatsGroupNegativeInt64(n)
if isNew {
shard.probablyMoveGroupMapToShards()
}
return psg
}
psm := shard.getGroupMapShardByUint64(uint64(n))
psg, _ := psm.getPipeStatsGroupNegativeInt64(n)
return psg
}
func (shard *pipeStatsProcessorShard) getPipeStatsGroupString(v []byte) *pipeStatsGroup {
if shard.groupMapShards == nil {
psg, isNew := shard.groupMap.getPipeStatsGroupString(v)
if isNew {
shard.probablyMoveGroupMapToShards()
}
return psg
}
psm := shard.getGroupMapShardByString(v)
psg, _ := psm.getPipeStatsGroupString(v)
return psg
}
func (shard *pipeStatsProcessorShard) probablyMoveGroupMapToShards() {
if shard.groupMap.entriesCount() < pipeStatsGroupMapMaxLen {
return
}
shard.moveGroupMapToShards()
}
func (shard *pipeStatsProcessorShard) moveGroupMapToShards() {
// set cpusCount to the number of shards, since this is the concurrency limit set by the caller.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8201
cpusCount := uint(len(shard.psp.shards))
bytesAllocatedPrev := shard.a.bytesAllocated
shard.groupMapShards = shard.a.newPipeStatsGroupMaps(cpusCount)
shard.stateSizeBudget -= shard.a.bytesAllocated - bytesAllocatedPrev
for i := range shard.groupMapShards {
shard.groupMapShards[i].init(shard)
}
for n, psg := range shard.groupMap.u64 {
psm := shard.getGroupMapShardByUint64(n)
psm.setPipeStatsGroupUint64(n, psg)
}
for n, psg := range shard.groupMap.negative64 {
psm := shard.getGroupMapShardByUint64(n)
psm.setPipeStatsGroupNegativeInt64(int64(n), psg)
}
for s, psg := range shard.groupMap.strings {
psm := shard.getGroupMapShardByString(bytesutil.ToUnsafeBytes(s))
psm.setPipeStatsGroupString(s, psg)
}
shard.groupMap.reset()
}
func (shard *pipeStatsProcessorShard) getGroupMapShardByString(v []byte) *pipeStatsGroupMap {
h := xxhash.Sum64(v)
shardIdx := h % uint64(len(shard.groupMapShards))
return &shard.groupMapShards[shardIdx]
}
func (shard *pipeStatsProcessorShard) getGroupMapShardByUint64(n uint64) *pipeStatsGroupMap {
h := fastHashUint64(n)
shardIdx := h % uint64(len(shard.groupMapShards))
return &shard.groupMapShards[shardIdx]
}
type pipeStatsGroup struct {
funcs []pipeStatsFunc
sfps []statsProcessor
@@ -793,7 +677,10 @@ func (psp *pipeStatsProcessor) flush() error {
}
// Merge states across shards in parallel
psms := psp.mergeShardsParallel()
psms, err := psp.mergeShardsParallel()
if err != nil {
return err
}
if needStop(psp.stopCh) {
return nil
}
@@ -802,8 +689,8 @@ func (psp *pipeStatsProcessor) flush() error {
// Special case - zero matching rows.
shard := &psp.shards[0]
shard.init()
shard.groupMap.getPipeStatsGroupString(nil)
psms = append(psms, &shard.groupMap)
_ = shard.m.getPipeStatsGroupString(nil)
psms = append(psms, &shard.m)
}
// Write the calculated stats in parallel to the next pipe.
@@ -958,48 +845,93 @@ func (psw *pipeStatsWriter) writeShardData(psm *pipeStatsGroupMap) {
}
}
func (psp *pipeStatsProcessor) mergeShardsParallel() []*pipeStatsGroupMap {
func (psp *pipeStatsProcessor) mergeShardsParallel() ([]*pipeStatsGroupMap, error) {
shards := psp.shards
var wg sync.WaitGroup
for i := range shards {
shard := &shards[i]
if shard.groupMapShards != nil {
continue
}
wg.Add(1)
go func() {
defer wg.Done()
shard.moveGroupMapToShards()
}()
}
wg.Wait()
if needStop(psp.stopCh) {
return nil
}
shardsLen := len(shards)
// set cpusCount to the number of shards, since this is the concurrency limit set by the caller.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8201
cpusCount := len(shards[0].groupMapShards)
cpusCount := len(shards)
if shardsLen == 1 {
var psms []*pipeStatsGroupMap
shard := &shards[0]
if shard.m.entriesCount() > 0 {
psms = append(psms, &shard.m)
}
return psms, nil
}
var wg sync.WaitGroup
perShardMaps := make([][]pipeStatsGroupMap, shardsLen)
for i := range shards {
wg.Add(1)
go func(idx int) {
defer wg.Done()
perCPU := make([]pipeStatsGroupMap, cpusCount)
for i := range perCPU {
perCPU[i].init(&shards[idx])
}
psm := &shards[idx].m
for n, psg := range psm.u64 {
if needStop(psp.stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].u64[n] = psg
}
for n, psg := range psm.negative64 {
if needStop(psp.stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].negative64[n] = psg
}
for k, psg := range psm.strings {
if needStop(psp.stopCh) {
return
}
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(k))
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].strings[k] = psg
}
perShardMaps[idx] = perCPU
psm.reset()
}(i)
}
wg.Wait()
if needStop(psp.stopCh) {
return nil, nil
}
// Merge per-shard entries into perShardMaps[0]
for i := 0; i < cpusCount; i++ {
wg.Add(1)
go func(cpuIdx int) {
defer wg.Done()
psm := &shards[0].groupMapShards[cpuIdx]
for j := range shards[1:] {
src := &shards[1+j].groupMapShards[cpuIdx]
psm.mergeState(src, psp.stopCh)
src.reset()
psm := &perShardMaps[0][cpuIdx]
for _, perCPU := range perShardMaps[1:] {
psm.mergeState(&perCPU[cpuIdx], psp.stopCh)
perCPU[cpuIdx].reset()
}
}(i)
}
wg.Wait()
if needStop(psp.stopCh) {
return nil
return nil, nil
}
// Filter out maps without entries
psms := shards[0].groupMapShards
psms := perShardMaps[0]
result := make([]*pipeStatsGroupMap, 0, len(psms))
for i := range psms {
if psms[i].entriesCount() > 0 {
@@ -1007,7 +939,7 @@ func (psp *pipeStatsProcessor) mergeShardsParallel() []*pipeStatsGroupMap {
}
}
return result
return result, nil
}
func parsePipeStats(lex *lexer, needStatsKeyword bool) (pipe, error) {

View File

@@ -94,7 +94,7 @@ func (pt *pipeTop) newPipeProcessor(workersCount int, stopCh <-chan struct{}, ca
pt: pt,
},
}
shards[i].m.init(uint(workersCount), &shards[i].stateSizeBudget)
shards[i].m.init(&shards[i].stateSizeBudget)
}
ptp := &pipeTopProcessor{
@@ -136,7 +136,7 @@ type pipeTopProcessorShardNopad struct {
pt *pipeTop
// m holds per-value hits.
m hitsMapAdaptive
m hitsMap
// keyBuf is a temporary buffer for building keys for m.
keyBuf []byte
@@ -389,17 +389,21 @@ func (ptp *pipeTopProcessor) mergeShardsParallel() []*pipeTopEntry {
return nil
}
hmas := make([]*hitsMapAdaptive, 0, len(ptp.shards))
hms := make([]*hitsMap, 0, len(ptp.shards))
for i := range ptp.shards {
hma := &ptp.shards[i].m
if hma.entriesCount() > 0 {
hmas = append(hmas, hma)
hm := &ptp.shards[i].m
if hm.entriesCount() > 0 {
hms = append(hms, hm)
}
}
// set cpusCount to the number of shards, since this is the concurrency limit set by the caller.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8201
cpusCount := len(ptp.shards)
var entries []*pipeTopEntry
var entriesLock sync.Mutex
hitsMapMergeParallel(hmas, ptp.stopCh, func(hm *hitsMap) {
hitsMapMergeParallel(hms, cpusCount, ptp.stopCh, func(hm *hitsMap) {
es := getTopEntries(hm, limit, ptp.stopCh)
entriesLock.Lock()
entries = append(entries, es...)

View File

@@ -77,7 +77,7 @@ func (pu *pipeUniq) newPipeProcessor(workersCount int, stopCh <-chan struct{}, c
pu: pu,
},
}
shards[i].m.init(uint(workersCount), &shards[i].stateSizeBudget)
shards[i].m.init(&shards[i].stateSizeBudget)
}
pup := &pipeUniqProcessor{
@@ -119,7 +119,7 @@ type pipeUniqProcessorShardNopad struct {
pu *pipeUniq
// m holds per-row hits.
m hitsMapAdaptive
m hitsMap
// keyBuf is a temporary buffer for building keys for m.
keyBuf []byte
@@ -462,17 +462,21 @@ func (pup *pipeUniqProcessor) writeShardData(workerID uint, hm *hitsMap, resetHi
}
func (pup *pipeUniqProcessor) mergeShardsParallel() []*hitsMap {
hmas := make([]*hitsMapAdaptive, 0, len(pup.shards))
hms := make([]*hitsMap, 0, len(pup.shards))
for i := range pup.shards {
hma := &pup.shards[i].m
if hma.entriesCount() > 0 {
hmas = append(hmas, hma)
hm := &pup.shards[i].m
if hm.entriesCount() > 0 {
hms = append(hms, hm)
}
}
var hmsResult []*hitsMap
// set cpusCount to the number of shards, since this is the concurrency limit set by the caller.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8201
cpusCount := len(pup.shards)
hmsResult := make([]*hitsMap, 0, cpusCount)
var hmsLock sync.Mutex
hitsMapMergeParallel(hmas, pup.stopCh, func(hm *hitsMap) {
hitsMapMergeParallel(hms, cpusCount, pup.stopCh, func(hm *hitsMap) {
if hm.entriesCount() > 0 {
hmsLock.Lock()
hmsResult = append(hmsResult, hm)

View File

@@ -9,6 +9,7 @@ import (
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
)
@@ -32,40 +33,21 @@ func (su *statsCountUniq) updateNeededFields(neededFields fieldsSet) {
func (su *statsCountUniq) newStatsProcessor(a *chunkedAllocator) statsProcessor {
sup := a.newStatsCountUniqProcessor()
sup.a = a
sup.m.init()
return sup
}
type statsCountUniqProcessor struct {
a *chunkedAllocator
// concurrency is the number of parallel workers to use when merging shards.
//
// this field must be updated by the caller before using statsCountUniqProcessor.
concurrency uint
// uniqValues is used for tracking small number of unique values until it reaches statsCountUniqValuesMaxLen.
// After that the unique values are tracked by shards.
uniqValues statsCountUniqSet
// shards are used for tracking big number of unique values.
//
// Every shard contains a share of unique values, which are merged in parallel at finalizeStats().
shards []statsCountUniqSet
// shardss is used for collecting shards from other statsCountUniqProcessor instances at mergeState().
shardss [][]statsCountUniqSet
m statsCountUniqSet
ms []*statsCountUniqSet
columnValues [][]string
keyBuf []byte
tmpNum int
}
// the maximum number of values to track in statsCountUniqProcessor.uniqValues before switching to statsCountUniqProcessor.shards
//
// Too big value may slow down mergeState() across big number of CPU cores.
// Too small value may significantly increase RAM usage when coun_uniq() is applied individually to big number of groups.
const statsCountUniqValuesMaxLen = 4 << 10
type statsCountUniqSet struct {
timestamps map[uint64]struct{}
u64 map[uint64]struct{}
@@ -74,7 +56,17 @@ type statsCountUniqSet struct {
}
func (sus *statsCountUniqSet) reset() {
*sus = statsCountUniqSet{}
sus.timestamps = nil
sus.u64 = nil
sus.negative64 = nil
sus.strings = nil
}
func (sus *statsCountUniqSet) init() {
sus.timestamps = make(map[uint64]struct{})
sus.u64 = make(map[uint64]struct{})
sus.negative64 = make(map[uint64]struct{})
sus.strings = make(map[string]struct{})
}
func (sus *statsCountUniqSet) entriesCount() uint64 {
@@ -83,33 +75,66 @@ func (sus *statsCountUniqSet) entriesCount() uint64 {
}
func (sus *statsCountUniqSet) updateStateTimestamp(ts int64) int {
return updateUint64Set(&sus.timestamps, uint64(ts))
_, ok := sus.timestamps[uint64(ts)]
if ok {
return 0
}
sus.timestamps[uint64(ts)] = struct{}{}
return 8
}
func (sus *statsCountUniqSet) updateStateUint64(n uint64) int {
return updateUint64Set(&sus.u64, n)
_, ok := sus.u64[n]
if ok {
return 0
}
sus.u64[n] = struct{}{}
return 8
}
func (sus *statsCountUniqSet) updateStateInt64(n int64) int {
if n >= 0 {
return sus.updateStateUint64(uint64(n))
}
return sus.updateStateNegativeInt64(n)
}
func (sus *statsCountUniqSet) updateStateNegativeInt64(n int64) int {
return updateUint64Set(&sus.negative64, uint64(n))
}
func (sus *statsCountUniqSet) updateStateString(a *chunkedAllocator, v []byte) int {
if _, ok := sus.strings[string(v)]; ok {
_, ok := sus.negative64[uint64(n)]
if ok {
return 0
}
vCopy := a.cloneBytesToString(v)
return setStringSet(&sus.strings, vCopy) + len(vCopy)
sus.negative64[uint64(n)] = struct{}{}
return 8
}
func (sus *statsCountUniqSet) updateStateGeneric(a *chunkedAllocator, v string) int {
if n, ok := tryParseUint64(v); ok {
return sus.updateStateUint64(n)
}
if len(v) > 0 && v[0] == '-' {
if n, ok := tryParseInt64(v); ok {
return sus.updateStateNegativeInt64(n)
}
}
return sus.updateStateString(a, v)
}
func (sus *statsCountUniqSet) updateStateString(a *chunkedAllocator, v string) int {
_, ok := sus.strings[v]
if ok {
return 0
}
vCopy := a.cloneString(v)
sus.strings[vCopy] = struct{}{}
return int(unsafe.Sizeof(v)) + len(v)
}
func (sus *statsCountUniqSet) mergeState(src *statsCountUniqSet, stopCh <-chan struct{}) {
mergeUint64Set(&sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(&sus.u64, src.u64, stopCh)
mergeUint64Set(&sus.negative64, src.negative64, stopCh)
mergeUint64Set(sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(sus.u64, src.u64, stopCh)
mergeUint64Set(sus.negative64, src.negative64, stopCh)
if sus.strings == nil {
sus.strings = make(map[string]struct{})
}
for k := range src.strings {
if needStop(stopCh) {
return
@@ -120,46 +145,7 @@ func (sus *statsCountUniqSet) mergeState(src *statsCountUniqSet, stopCh <-chan s
}
}
func updateUint64Set(dstPtr *map[uint64]struct{}, n uint64) int {
dst := *dstPtr
if _, ok := dst[n]; ok {
return 0
}
return setUint64Set(dstPtr, n)
}
func setUint64Set(dstPtr *map[uint64]struct{}, n uint64) int {
dst := *dstPtr
if dst == nil {
dst = map[uint64]struct{}{
n: {},
}
*dstPtr = dst
return int(unsafe.Sizeof(dst) + unsafe.Sizeof(n))
}
dst[n] = struct{}{}
return int(unsafe.Sizeof(n))
}
func setStringSet(dstPtr *map[string]struct{}, v string) int {
dst := *dstPtr
if dst == nil {
dst = map[string]struct{}{
v: {},
}
*dstPtr = dst
return int(unsafe.Sizeof(dst) + unsafe.Sizeof(v))
}
dst[v] = struct{}{}
return int(unsafe.Sizeof(v))
}
func mergeUint64Set(dstPtr *map[uint64]struct{}, src map[uint64]struct{}, stopCh <-chan struct{}) {
dst := *dstPtr
if dst == nil {
dst = make(map[uint64]struct{})
*dstPtr = dst
}
func mergeUint64Set(dst map[uint64]struct{}, src map[uint64]struct{}, stopCh <-chan struct{}) {
for n := range src {
if needStop(stopCh) {
return
@@ -332,12 +318,12 @@ func (sup *statsCountUniqProcessor) updateStatsForAllRowsSingleColumn(br *blockR
if c.isTime {
// Count unique timestamps
timestamps := br.getTimestamps()
for i := range timestamps {
for i, timestamp := range timestamps {
if i > 0 && timestamps[i-1] == timestamps[i] {
// This timestamp has been already counted.
continue
}
stateSizeIncrease += sup.updateStateTimestamp(timestamps[i])
stateSizeIncrease += sup.m.updateStateTimestamp(timestamp)
}
return stateSizeIncrease
}
@@ -370,7 +356,7 @@ func (sup *statsCountUniqProcessor) updateStatsForAllRowsSingleColumn(br *blockR
continue
}
n := unmarshalUint8(v)
stateSizeIncrease += sup.updateStateUint64(uint64(n))
stateSizeIncrease += sup.m.updateStateUint64(uint64(n))
}
return stateSizeIncrease
case valueTypeUint16:
@@ -380,7 +366,7 @@ func (sup *statsCountUniqProcessor) updateStatsForAllRowsSingleColumn(br *blockR
continue
}
n := unmarshalUint16(v)
stateSizeIncrease += sup.updateStateUint64(uint64(n))
stateSizeIncrease += sup.m.updateStateUint64(uint64(n))
}
return stateSizeIncrease
case valueTypeUint32:
@@ -390,7 +376,7 @@ func (sup *statsCountUniqProcessor) updateStatsForAllRowsSingleColumn(br *blockR
continue
}
n := unmarshalUint32(v)
stateSizeIncrease += sup.updateStateUint64(uint64(n))
stateSizeIncrease += sup.m.updateStateUint64(uint64(n))
}
return stateSizeIncrease
case valueTypeUint64:
@@ -400,7 +386,7 @@ func (sup *statsCountUniqProcessor) updateStatsForAllRowsSingleColumn(br *blockR
continue
}
n := unmarshalUint64(v)
stateSizeIncrease += sup.updateStateUint64(n)
stateSizeIncrease += sup.m.updateStateUint64(n)
}
return stateSizeIncrease
case valueTypeInt64:
@@ -410,7 +396,7 @@ func (sup *statsCountUniqProcessor) updateStatsForAllRowsSingleColumn(br *blockR
continue
}
n := unmarshalInt64(v)
stateSizeIncrease += sup.updateStateInt64(n)
stateSizeIncrease += sup.m.updateStateInt64(n)
}
return stateSizeIncrease
default:
@@ -435,7 +421,8 @@ func (sup *statsCountUniqProcessor) updateStatsForRowSingleColumn(br *blockResul
if c.isTime {
// Count unique timestamps
timestamps := br.getTimestamps()
return sup.updateStateTimestamp(timestamps[rowIdx])
timestamp := timestamps[rowIdx]
return sup.m.updateStateTimestamp(timestamp)
}
if c.isConst {
// count unique const values
@@ -462,27 +449,27 @@ func (sup *statsCountUniqProcessor) updateStatsForRowSingleColumn(br *blockResul
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint8(v)
return sup.updateStateUint64(uint64(n))
return sup.m.updateStateUint64(uint64(n))
case valueTypeUint16:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint16(v)
return sup.updateStateUint64(uint64(n))
return sup.m.updateStateUint64(uint64(n))
case valueTypeUint32:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint32(v)
return sup.updateStateUint64(uint64(n))
return sup.m.updateStateUint64(uint64(n))
case valueTypeUint64:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint64(v)
return sup.updateStateUint64(n)
return sup.m.updateStateUint64(n)
case valueTypeInt64:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalInt64(v)
return sup.updateStateInt64(n)
return sup.m.updateStateInt64(n)
default:
// Count unique values for the given rowIdx
v := c.getValueAtRow(br, rowIdx)
@@ -501,32 +488,20 @@ func (sup *statsCountUniqProcessor) mergeState(sf statsFunc, sfp statsProcessor)
}
src := sfp.(*statsCountUniqProcessor)
if sup.shards == nil {
if src.shards == nil {
sup.uniqValues.mergeState(&src.uniqValues, nil)
src.uniqValues.reset()
sup.probablyMoveUniqValuesToShards()
return
}
sup.moveUniqValuesToShards()
if src.m.entriesCount() > 100_000 {
// Postpone merging too big number of items in parallel
sup.ms = append(sup.ms, &src.m)
return
}
if src.shards == nil {
src.moveUniqValuesToShards()
}
sup.shardss = append(sup.shardss, src.shards)
src.shards = nil
sup.m.mergeState(&src.m, nil)
}
func (sup *statsCountUniqProcessor) finalizeStats(sf statsFunc, dst []byte, stopCh <-chan struct{}) []byte {
n := sup.entriesCount()
if len(sup.shardss) > 0 {
if sup.shards != nil {
sup.shardss = append(sup.shardss, sup.shards)
sup.shards = nil
}
n = countUniqParallel(sup.shardss, stopCh)
n := sup.m.entriesCount()
if len(sup.ms) > 0 {
sup.ms = append(sup.ms, &sup.m)
n = countUniqParallel(sup.ms, stopCh)
}
su := sf.(*statsCountUniq)
@@ -536,22 +511,78 @@ func (sup *statsCountUniqProcessor) finalizeStats(sf statsFunc, dst []byte, stop
return strconv.AppendUint(dst, n, 10)
}
func countUniqParallel(shardss [][]statsCountUniqSet, stopCh <-chan struct{}) uint64 {
cpusCount := len(shardss[0])
perCPUCounts := make([]uint64, cpusCount)
func countUniqParallel(ms []*statsCountUniqSet, stopCh <-chan struct{}) uint64 {
shardsLen := len(ms)
cpusCount := cgroup.AvailableCPUs()
var wg sync.WaitGroup
msShards := make([][]statsCountUniqSet, shardsLen)
for i := range msShards {
wg.Add(1)
go func(idx int) {
defer wg.Done()
perCPU := make([]statsCountUniqSet, cpusCount)
for i := range perCPU {
perCPU[i].init()
}
sus := ms[idx]
for ts := range sus.timestamps {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&ts)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].timestamps[ts] = struct{}{}
}
for n := range sus.u64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].u64[n] = struct{}{}
}
for n := range sus.negative64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].negative64[n] = struct{}{}
}
for k := range sus.strings {
if needStop(stopCh) {
return
}
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(k))
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].strings[k] = struct{}{}
}
msShards[idx] = perCPU
ms[idx].reset()
}(i)
}
wg.Wait()
perCPUCounts := make([]uint64, cpusCount)
for i := range perCPUCounts {
wg.Add(1)
go func(cpuIdx int) {
defer wg.Done()
sus := &shardss[0][cpuIdx]
for _, perCPU := range shardss[1:] {
sus := &msShards[0][cpuIdx]
for _, perCPU := range msShards[1:] {
sus.mergeState(&perCPU[cpuIdx], stopCh)
perCPU[cpuIdx].reset()
}
perCPUCounts[cpuIdx] = sus.entriesCount()
sus.reset()
}(i)
}
wg.Wait()
@@ -563,130 +594,12 @@ func countUniqParallel(shardss [][]statsCountUniqSet, stopCh <-chan struct{}) ui
return countTotal
}
func (sup *statsCountUniqProcessor) entriesCount() uint64 {
if sup.shards == nil {
return sup.uniqValues.entriesCount()
}
n := uint64(0)
shards := sup.shards
for i := range shards {
n += shards[i].entriesCount()
}
return n
}
func (sup *statsCountUniqProcessor) updateStateGeneric(v string) int {
if n, ok := tryParseUint64(v); ok {
return sup.updateStateUint64(n)
}
if len(v) > 0 && v[0] == '-' {
if n, ok := tryParseInt64(v); ok {
return sup.updateStateNegativeInt64(n)
}
}
return sup.updateStateString(bytesutil.ToUnsafeBytes(v))
}
func (sup *statsCountUniqProcessor) updateStateInt64(n int64) int {
if n >= 0 {
return sup.updateStateUint64(uint64(n))
}
return sup.updateStateNegativeInt64(n)
return sup.m.updateStateGeneric(sup.a, v)
}
func (sup *statsCountUniqProcessor) updateStateString(v []byte) int {
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateString(sup.a, v)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
sus := sup.getShardByString(v)
return sus.updateStateString(sup.a, v)
}
func (sup *statsCountUniqProcessor) updateStateTimestamp(ts int64) int {
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateTimestamp(ts)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
sus := sup.getShardByUint64(uint64(ts))
return sus.updateStateTimestamp(ts)
}
func (sup *statsCountUniqProcessor) updateStateUint64(n uint64) int {
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateUint64(n)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
sus := sup.getShardByUint64(n)
return sus.updateStateUint64(n)
}
func (sup *statsCountUniqProcessor) updateStateNegativeInt64(n int64) int {
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateNegativeInt64(n)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
sus := sup.getShardByUint64(uint64(n))
return sus.updateStateNegativeInt64(n)
}
func (sup *statsCountUniqProcessor) probablyMoveUniqValuesToShards() int {
if sup.uniqValues.entriesCount() < statsCountUniqValuesMaxLen {
return 0
}
return sup.moveUniqValuesToShards()
}
func (sup *statsCountUniqProcessor) moveUniqValuesToShards() int {
cpusCount := sup.concurrency
bytesAllocatedPrev := sup.a.bytesAllocated
sup.shards = sup.a.newStatsCountUniqSets(cpusCount)
stateSizeIncrease := sup.a.bytesAllocated - bytesAllocatedPrev
for ts := range sup.uniqValues.timestamps {
sus := sup.getShardByUint64(ts)
setUint64Set(&sus.timestamps, ts)
}
for n := range sup.uniqValues.u64 {
sus := sup.getShardByUint64(n)
setUint64Set(&sus.u64, n)
}
for n := range sup.uniqValues.negative64 {
sus := sup.getShardByUint64(n)
setUint64Set(&sus.negative64, n)
}
for s := range sup.uniqValues.strings {
sus := sup.getShardByString(bytesutil.ToUnsafeBytes(s))
setStringSet(&sus.strings, s)
}
sup.uniqValues.reset()
return stateSizeIncrease
}
func (sup *statsCountUniqProcessor) getShardByString(v []byte) *statsCountUniqSet {
h := xxhash.Sum64(v)
cpuIdx := h % uint64(len(sup.shards))
return &sup.shards[cpuIdx]
}
func (sup *statsCountUniqProcessor) getShardByUint64(n uint64) *statsCountUniqSet {
h := fastHashUint64(n)
cpuIdx := h % uint64(len(sup.shards))
return &sup.shards[cpuIdx]
return sup.m.updateStateString(sup.a, bytesutil.ToUnsafeString(v))
}
func (sup *statsCountUniqProcessor) limitReached(su *statsCountUniq) bool {
@@ -694,7 +607,7 @@ func (sup *statsCountUniqProcessor) limitReached(su *statsCountUniq) bool {
if limit <= 0 {
return false
}
return sup.entriesCount() > limit
return sup.m.entriesCount() > limit
}
func parseStatsCountUniq(lex *lexer) (*statsCountUniq, error) {

View File

@@ -4,10 +4,12 @@ import (
"fmt"
"strconv"
"sync"
"unsafe"
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
)
@@ -31,40 +33,21 @@ func (su *statsCountUniqHash) updateNeededFields(neededFields fieldsSet) {
func (su *statsCountUniqHash) newStatsProcessor(a *chunkedAllocator) statsProcessor {
sup := a.newStatsCountUniqHashProcessor()
sup.a = a
sup.m.init()
return sup
}
type statsCountUniqHashProcessor struct {
a *chunkedAllocator
// concurrency is the number of parallel workers to use when merging shards.
//
// this field must be updated by the caller before using statsCountUniqHashProcessor.
concurrency uint
// uniqValues is used for tracking small number of unique values until it reaches statsCountUniqHashValuesMaxLen.
// After that the unique values are tracked by shards.
uniqValues statsCountUniqHashSet
// shards are used for tracking big number of unique values.
//
// Every shard contains a share of unique values, which are merged in parallel at finalizeStats().
shards []statsCountUniqHashSet
// shardss is used for collecting shards from other statsCountUniqProcessor instances at mergeState().
shardss [][]statsCountUniqHashSet
m statsCountUniqHashSet
ms []*statsCountUniqHashSet
columnValues [][]string
keyBuf []byte
tmpNum int
}
// the maximum number of values to track in statsCountUniqHashProcessor.uniqValues before switching to statsCountUniqHashProcessor.shards
//
// Too big value may slow down mergeState() across big number of CPU cores.
// Too small value may significantly increase RAM usage when coun_uniq_hash() is applied individually to big number of groups.
const statsCountUniqHashValuesMaxLen = 4 << 10
type statsCountUniqHashSet struct {
timestamps map[uint64]struct{}
u64 map[uint64]struct{}
@@ -73,7 +56,17 @@ type statsCountUniqHashSet struct {
}
func (sus *statsCountUniqHashSet) reset() {
*sus = statsCountUniqHashSet{}
sus.timestamps = nil
sus.u64 = nil
sus.negative64 = nil
sus.strings = nil
}
func (sus *statsCountUniqHashSet) init() {
sus.timestamps = make(map[uint64]struct{})
sus.u64 = make(map[uint64]struct{})
sus.negative64 = make(map[uint64]struct{})
sus.strings = make(map[uint64]struct{})
}
func (sus *statsCountUniqHashSet) entriesCount() uint64 {
@@ -82,26 +75,66 @@ func (sus *statsCountUniqHashSet) entriesCount() uint64 {
}
func (sus *statsCountUniqHashSet) updateStateTimestamp(ts int64) int {
return updateUint64Set(&sus.timestamps, uint64(ts))
_, ok := sus.timestamps[uint64(ts)]
if ok {
return 0
}
sus.timestamps[uint64(ts)] = struct{}{}
return 8
}
func (sus *statsCountUniqHashSet) updateStateUint64(n uint64) int {
return updateUint64Set(&sus.timestamps, n)
_, ok := sus.u64[n]
if ok {
return 0
}
sus.u64[n] = struct{}{}
return 8
}
func (sus *statsCountUniqHashSet) updateStateInt64(n int64) int {
if n >= 0 {
return sus.updateStateUint64(uint64(n))
}
return sus.updateStateNegativeInt64(n)
}
func (sus *statsCountUniqHashSet) updateStateNegativeInt64(n int64) int {
return updateUint64Set(&sus.negative64, uint64(n))
_, ok := sus.negative64[uint64(n)]
if ok {
return 0
}
sus.negative64[uint64(n)] = struct{}{}
return 8
}
func (sus *statsCountUniqHashSet) updateStateStringHash(h uint64) int {
return updateUint64Set(&sus.strings, h)
func (sus *statsCountUniqHashSet) updateStateGeneric(v string) int {
if n, ok := tryParseUint64(v); ok {
return sus.updateStateUint64(n)
}
if len(v) > 0 && v[0] == '-' {
if n, ok := tryParseInt64(v); ok {
return sus.updateStateNegativeInt64(n)
}
}
return sus.updateStateString(bytesutil.ToUnsafeBytes(v))
}
func (sus *statsCountUniqHashSet) updateStateString(v []byte) int {
h := xxhash.Sum64(v)
_, ok := sus.strings[h]
if ok {
return 0
}
sus.strings[h] = struct{}{}
return 8
}
func (sus *statsCountUniqHashSet) mergeState(src *statsCountUniqHashSet, stopCh <-chan struct{}) {
mergeUint64Set(&sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(&sus.u64, src.u64, stopCh)
mergeUint64Set(&sus.negative64, src.negative64, stopCh)
mergeUint64Set(&sus.strings, src.strings, stopCh)
mergeUint64Set(sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(sus.u64, src.u64, stopCh)
mergeUint64Set(sus.negative64, src.negative64, stopCh)
mergeUint64Set(sus.strings, src.strings, stopCh)
}
func (sup *statsCountUniqHashProcessor) updateStatsForAllRows(sf statsFunc, br *blockResult) int {
@@ -153,7 +186,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRows(sf statsFunc, br *
// Do not count empty values
continue
}
stateSizeIncrease += sup.updateStateString(keyBuf)
stateSizeIncrease += sup.m.updateStateString(keyBuf)
}
sup.keyBuf = keyBuf
return stateSizeIncrease
@@ -200,7 +233,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRows(sf statsFunc, br *
// Do not count empty values
continue
}
stateSizeIncrease += sup.updateStateString(keyBuf)
stateSizeIncrease += sup.m.updateStateString(keyBuf)
}
sup.keyBuf = keyBuf
return stateSizeIncrease
@@ -233,7 +266,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForRow(sf statsFunc, br *bloc
// Do not count empty values
return 0
}
return sup.updateStateString(keyBuf)
return sup.m.updateStateString(keyBuf)
}
if len(fields) == 1 {
// Fast path for a single column.
@@ -257,7 +290,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForRow(sf statsFunc, br *bloc
// Do not count empty values
return 0
}
return sup.updateStateString(keyBuf)
return sup.m.updateStateString(keyBuf)
}
func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *blockResult, columnName string) int {
@@ -266,12 +299,12 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
if c.isTime {
// Count unique timestamps
timestamps := br.getTimestamps()
for i := range timestamps {
for i, timestamp := range timestamps {
if i > 0 && timestamps[i-1] == timestamps[i] {
// This timestamp has been already counted.
continue
}
stateSizeIncrease += sup.updateStateTimestamp(timestamps[i])
stateSizeIncrease += sup.m.updateStateTimestamp(timestamp)
}
return stateSizeIncrease
}
@@ -282,7 +315,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
// Do not count empty values
return 0
}
return sup.updateStateGeneric(v)
return sup.m.updateStateGeneric(v)
}
switch c.valueType {
@@ -294,7 +327,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
// Do not count empty values
return
}
sup.tmpNum += sup.updateStateGeneric(v)
sup.tmpNum += sup.m.updateStateGeneric(v)
})
return sup.tmpNum
case valueTypeUint8:
@@ -304,7 +337,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
continue
}
n := unmarshalUint8(v)
stateSizeIncrease += sup.updateStateUint64(uint64(n))
stateSizeIncrease += sup.m.updateStateUint64(uint64(n))
}
return stateSizeIncrease
case valueTypeUint16:
@@ -314,7 +347,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
continue
}
n := unmarshalUint16(v)
stateSizeIncrease += sup.updateStateUint64(uint64(n))
stateSizeIncrease += sup.m.updateStateUint64(uint64(n))
}
return stateSizeIncrease
case valueTypeUint32:
@@ -324,7 +357,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
continue
}
n := unmarshalUint32(v)
stateSizeIncrease += sup.updateStateUint64(uint64(n))
stateSizeIncrease += sup.m.updateStateUint64(uint64(n))
}
return stateSizeIncrease
case valueTypeUint64:
@@ -334,7 +367,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
continue
}
n := unmarshalUint64(v)
stateSizeIncrease += sup.updateStateUint64(n)
stateSizeIncrease += sup.m.updateStateUint64(n)
}
return stateSizeIncrease
case valueTypeInt64:
@@ -344,7 +377,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
continue
}
n := unmarshalInt64(v)
stateSizeIncrease += sup.updateStateInt64(n)
stateSizeIncrease += sup.m.updateStateInt64(n)
}
return stateSizeIncrease
default:
@@ -359,7 +392,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForAllRowsSingleColumn(br *bl
// This value has been already counted.
continue
}
stateSizeIncrease += sup.updateStateGeneric(v)
stateSizeIncrease += sup.m.updateStateGeneric(v)
}
return stateSizeIncrease
}
@@ -370,7 +403,8 @@ func (sup *statsCountUniqHashProcessor) updateStatsForRowSingleColumn(br *blockR
if c.isTime {
// Count unique timestamps
timestamps := br.getTimestamps()
return sup.updateStateTimestamp(timestamps[rowIdx])
timestamp := timestamps[rowIdx]
return sup.m.updateStateTimestamp(timestamp)
}
if c.isConst {
// count unique const values
@@ -379,7 +413,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForRowSingleColumn(br *blockR
// Do not count empty values
return 0
}
return sup.updateStateGeneric(v)
return sup.m.updateStateGeneric(v)
}
switch c.valueType {
@@ -392,32 +426,32 @@ func (sup *statsCountUniqHashProcessor) updateStatsForRowSingleColumn(br *blockR
// Do not count empty values
return 0
}
return sup.updateStateGeneric(v)
return sup.m.updateStateGeneric(v)
case valueTypeUint8:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint8(v)
return sup.updateStateUint64(uint64(n))
return sup.m.updateStateUint64(uint64(n))
case valueTypeUint16:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint16(v)
return sup.updateStateUint64(uint64(n))
return sup.m.updateStateUint64(uint64(n))
case valueTypeUint32:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint32(v)
return sup.updateStateUint64(uint64(n))
return sup.m.updateStateUint64(uint64(n))
case valueTypeUint64:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint64(v)
return sup.updateStateUint64(n)
return sup.m.updateStateUint64(n)
case valueTypeInt64:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalInt64(v)
return sup.updateStateInt64(n)
return sup.m.updateStateInt64(n)
default:
// Count unique values for the given rowIdx
v := c.getValueAtRow(br, rowIdx)
@@ -425,7 +459,7 @@ func (sup *statsCountUniqHashProcessor) updateStatsForRowSingleColumn(br *blockR
// Do not count empty values
return 0
}
return sup.updateStateGeneric(v)
return sup.m.updateStateGeneric(v)
}
}
@@ -436,57 +470,100 @@ func (sup *statsCountUniqHashProcessor) mergeState(sf statsFunc, sfp statsProces
}
src := sfp.(*statsCountUniqHashProcessor)
if sup.shards == nil {
if src.shards == nil {
sup.uniqValues.mergeState(&src.uniqValues, nil)
src.uniqValues.reset()
sup.probablyMoveUniqValuesToShards()
return
}
sup.moveUniqValuesToShards()
if src.m.entriesCount() > 100_000 {
// Postpone merging too big number of items in parallel
sup.ms = append(sup.ms, &src.m)
return
}
if src.shards == nil {
src.moveUniqValuesToShards()
}
sup.shardss = append(sup.shardss, src.shards)
src.shards = nil
sup.m.mergeState(&src.m, nil)
}
func (sup *statsCountUniqHashProcessor) finalizeStats(sf statsFunc, dst []byte, stopCh <-chan struct{}) []byte {
n := sup.entriesCount()
if len(sup.shardss) > 0 {
if sup.shards != nil {
sup.shardss = append(sup.shardss, sup.shards)
sup.shards = nil
}
n = countUniqHashParallel(sup.shardss, stopCh)
su := sf.(*statsCountUniqHash)
n := sup.m.entriesCount()
if len(sup.ms) > 0 {
sup.ms = append(sup.ms, &sup.m)
n = countUniqHashParallel(sup.ms, stopCh)
}
su := sf.(*statsCountUniqHash)
if limit := su.limit; limit > 0 && n > limit {
n = limit
}
return strconv.AppendUint(dst, n, 10)
}
func countUniqHashParallel(shardss [][]statsCountUniqHashSet, stopCh <-chan struct{}) uint64 {
cpusCount := len(shardss[0])
perCPUCounts := make([]uint64, cpusCount)
func countUniqHashParallel(ms []*statsCountUniqHashSet, stopCh <-chan struct{}) uint64 {
shardsLen := len(ms)
cpusCount := cgroup.AvailableCPUs()
var wg sync.WaitGroup
msShards := make([][]statsCountUniqHashSet, shardsLen)
for i := range msShards {
wg.Add(1)
go func(idx int) {
defer wg.Done()
perCPU := make([]statsCountUniqHashSet, cpusCount)
for i := range perCPU {
perCPU[i].init()
}
sus := ms[idx]
for ts := range sus.timestamps {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&ts)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].timestamps[ts] = struct{}{}
}
for n := range sus.u64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].u64[n] = struct{}{}
}
for n := range sus.negative64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].negative64[n] = struct{}{}
}
for h := range sus.strings {
if needStop(stopCh) {
return
}
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].strings[h] = struct{}{}
}
msShards[idx] = perCPU
ms[idx].reset()
}(i)
}
wg.Wait()
perCPUCounts := make([]uint64, cpusCount)
for i := range perCPUCounts {
wg.Add(1)
go func(cpuIdx int) {
defer wg.Done()
sus := &shardss[0][cpuIdx]
for _, perCPU := range shardss[1:] {
sus := &msShards[0][cpuIdx]
for _, perCPU := range msShards[1:] {
sus.mergeState(&perCPU[cpuIdx], stopCh)
perCPU[cpuIdx].reset()
}
perCPUCounts[cpuIdx] = sus.entriesCount()
sus.reset()
}(i)
}
wg.Wait()
@@ -498,142 +575,12 @@ func countUniqHashParallel(shardss [][]statsCountUniqHashSet, stopCh <-chan stru
return countTotal
}
func (sup *statsCountUniqHashProcessor) entriesCount() uint64 {
if sup.shards == nil {
return sup.uniqValues.entriesCount()
}
n := uint64(0)
shards := sup.shards
for i := range shards {
n += shards[i].entriesCount()
}
return n
}
func (sup *statsCountUniqHashProcessor) updateStateGeneric(v string) int {
if n, ok := tryParseUint64(v); ok {
return sup.updateStateUint64(n)
}
if len(v) > 0 && v[0] == '-' {
if n, ok := tryParseInt64(v); ok {
return sup.updateStateNegativeInt64(n)
}
}
return sup.updateStateString(bytesutil.ToUnsafeBytes(v))
}
func (sup *statsCountUniqHashProcessor) updateStateInt64(n int64) int {
if n >= 0 {
return sup.updateStateUint64(uint64(n))
}
return sup.updateStateNegativeInt64(n)
}
func (sup *statsCountUniqHashProcessor) updateStateString(v []byte) int {
h := xxhash.Sum64(v)
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateStringHash(h)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
return sup.updateStateStringHash(h)
}
func (sup *statsCountUniqHashProcessor) updateStateStringHash(h uint64) int {
sus := sup.getShardByStringHash(h)
return sus.updateStateStringHash(h)
}
func (sup *statsCountUniqHashProcessor) updateStateTimestamp(ts int64) int {
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateTimestamp(ts)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
sus := sup.getShardByUint64(uint64(ts))
return sus.updateStateTimestamp(ts)
}
func (sup *statsCountUniqHashProcessor) updateStateUint64(n uint64) int {
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateUint64(n)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
sus := sup.getShardByUint64(n)
return sus.updateStateUint64(n)
}
func (sup *statsCountUniqHashProcessor) updateStateNegativeInt64(n int64) int {
if sup.shards == nil {
stateSizeIncrease := sup.uniqValues.updateStateNegativeInt64(n)
if stateSizeIncrease > 0 {
stateSizeIncrease += sup.probablyMoveUniqValuesToShards()
}
return stateSizeIncrease
}
sus := sup.getShardByUint64(uint64(n))
return sus.updateStateNegativeInt64(n)
}
func (sup *statsCountUniqHashProcessor) probablyMoveUniqValuesToShards() int {
if sup.uniqValues.entriesCount() < statsCountUniqHashValuesMaxLen {
return 0
}
return sup.moveUniqValuesToShards()
}
func (sup *statsCountUniqHashProcessor) moveUniqValuesToShards() int {
cpusCount := sup.concurrency
bytesAllocatedPrev := sup.a.bytesAllocated
sup.shards = sup.a.newStatsCountUniqHashSets(cpusCount)
stateSizeIncrease := sup.a.bytesAllocated - bytesAllocatedPrev
for ts := range sup.uniqValues.timestamps {
sus := sup.getShardByUint64(ts)
setUint64Set(&sus.timestamps, ts)
}
for n := range sup.uniqValues.u64 {
sus := sup.getShardByUint64(n)
setUint64Set(&sus.u64, n)
}
for n := range sup.uniqValues.negative64 {
sus := sup.getShardByUint64(n)
setUint64Set(&sus.negative64, n)
}
for h := range sup.uniqValues.strings {
sus := sup.getShardByStringHash(h)
setUint64Set(&sus.strings, h)
}
sup.uniqValues.reset()
return stateSizeIncrease
}
func (sup *statsCountUniqHashProcessor) getShardByStringHash(h uint64) *statsCountUniqHashSet {
cpuIdx := h % uint64(len(sup.shards))
return &sup.shards[cpuIdx]
}
func (sup *statsCountUniqHashProcessor) getShardByUint64(n uint64) *statsCountUniqHashSet {
h := fastHashUint64(n)
cpuIdx := h % uint64(len(sup.shards))
return &sup.shards[cpuIdx]
}
func (sup *statsCountUniqHashProcessor) limitReached(su *statsCountUniqHash) bool {
limit := su.limit
if limit <= 0 {
return false
}
return sup.entriesCount() > limit
return sup.m.entriesCount() > limit
}
func parseStatsCountUniqHash(lex *lexer) (*statsCountUniqHash, error) {
@@ -655,10 +602,3 @@ func parseStatsCountUniqHash(lex *lexer) (*statsCountUniqHash, error) {
}
return su, nil
}
func fastHashUint64(x uint64) uint64 {
x ^= x >> 12 // a
x ^= x << 25 // b
x ^= x >> 27 // c
return x * 2685821657736338717
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/valyala/quicktemplate"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
)
type statsUniqValues struct {
@@ -40,11 +41,6 @@ func (su *statsUniqValues) newStatsProcessor(a *chunkedAllocator) statsProcessor
type statsUniqValuesProcessor struct {
a *chunkedAllocator
// concurrency is the number of parallel workers to use when merging shards.
//
// this field must be updated by the caller before using statsUniqValuesProcessor.
concurrency uint
m map[string]struct{}
ms []map[string]struct{}
}
@@ -166,7 +162,7 @@ func (sup *statsUniqValuesProcessor) finalizeStats(sf statsFunc, dst []byte, sto
var items []string
if len(sup.ms) > 0 {
sup.ms = append(sup.ms, sup.m)
items = mergeSetsParallel(sup.ms, sup.concurrency, stopCh)
items = mergeSetsParallel(sup.ms, stopCh)
} else {
items = setToSortedSlice(sup.m)
}
@@ -178,9 +174,9 @@ func (sup *statsUniqValuesProcessor) finalizeStats(sf statsFunc, dst []byte, sto
return marshalJSONArray(dst, items)
}
func mergeSetsParallel(ms []map[string]struct{}, concurrency uint, stopCh <-chan struct{}) []string {
func mergeSetsParallel(ms []map[string]struct{}, stopCh <-chan struct{}) []string {
shardsLen := len(ms)
cpusCount := concurrency
cpusCount := cgroup.AvailableCPUs()
var wg sync.WaitGroup
msShards := make([][]map[string]struct{}, shardsLen)