mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-07-30 20:41:44 +03:00
Compare commits
3 Commits
vmui/show-
...
zero-date
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2f0aab898 | ||
|
|
baa947c8d5 | ||
|
|
4f104d1ce5 |
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useImperativeHandle, useRef } from "preact/compat";
|
||||
import { FC, useRef } from "preact/compat";
|
||||
import ServerConfigurator from "./ServerConfigurator/ServerConfigurator";
|
||||
import { ArrowDownIcon, SettingsIcon } from "../../Main/Icons";
|
||||
import Button from "../../Main/Button/Button";
|
||||
@@ -19,11 +19,7 @@ export interface ChildComponentHandle {
|
||||
handleApply: () => void;
|
||||
}
|
||||
|
||||
export interface GlobalSettingsHandle {
|
||||
open: () => void;
|
||||
}
|
||||
|
||||
const GlobalSettings = forwardRef<GlobalSettingsHandle>((_, ref) => {
|
||||
const GlobalSettings: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const appModeEnable = getAppModeEnable();
|
||||
@@ -70,10 +66,6 @@ const GlobalSettings = forwardRef<GlobalSettingsHandle>((_, ref) => {
|
||||
}
|
||||
].filter(control => control.show);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: handleOpen,
|
||||
}));
|
||||
|
||||
return <>
|
||||
{isMobile ? (
|
||||
<div
|
||||
@@ -139,6 +131,6 @@ const GlobalSettings = forwardRef<GlobalSettingsHandle>((_, ref) => {
|
||||
</Modal>
|
||||
)}
|
||||
</>;
|
||||
});
|
||||
};
|
||||
|
||||
export default GlobalSettings;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { FC } from "preact/compat";
|
||||
import Button from "../../../Main/Button/Button";
|
||||
import { useTimeState } from "../../../../state/time/TimeStateContext";
|
||||
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||
import { getUTCByTimezone } from "../../../../utils/time";
|
||||
import { useMemo } from "react";
|
||||
import { ArrowDownIcon, PlanetIcon } from "../../../Main/Icons";
|
||||
|
||||
type Props = {
|
||||
onOpenSettings?: () => void;
|
||||
}
|
||||
|
||||
const TimeZonePreview: FC<Props> = ({ onOpenSettings }) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const { timezone } = useTimeState();
|
||||
const utcOffset = useMemo(() => getUTCByTimezone(timezone), [timezone]);
|
||||
|
||||
const handleOpenSettings = () => {
|
||||
onOpenSettings && onOpenSettings();
|
||||
};
|
||||
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<button
|
||||
className="vm-mobile-option"
|
||||
onClick={handleOpenSettings}
|
||||
>
|
||||
<span className="vm-mobile-option__icon"><PlanetIcon/></span>
|
||||
<div className="vm-mobile-option-text">
|
||||
<span className="vm-mobile-option-text__label">Time zone</span>
|
||||
<span className="vm-mobile-option-text__value">{utcOffset}</span>
|
||||
</div>
|
||||
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="vm-header-button"
|
||||
onClick={handleOpenSettings}
|
||||
startIcon={<PlanetIcon/>}
|
||||
>
|
||||
{utcOffset}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default TimeZonePreview;
|
||||
@@ -117,8 +117,6 @@ const StepConfigurator: FC = () => {
|
||||
if (step === value || step === defaultStep) handleApply(defaultStep);
|
||||
}, [isHistogram, displayType]);
|
||||
|
||||
const textValue = isAutoStep ? `auto (${customStep})` : customStep;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="vm-step-control"
|
||||
@@ -132,7 +130,7 @@ const StepConfigurator: FC = () => {
|
||||
<span className="vm-mobile-option__icon"><TimelineIcon/></span>
|
||||
<div className="vm-mobile-option-text">
|
||||
<span className="vm-mobile-option-text__label">Step</span>
|
||||
<span className="vm-mobile-option-text__value">{textValue}</span>
|
||||
<span className="vm-mobile-option-text__value">{customStep}</span>
|
||||
</div>
|
||||
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||
</div>
|
||||
@@ -144,7 +142,7 @@ const StepConfigurator: FC = () => {
|
||||
startIcon={<TimelineIcon/>}
|
||||
onClick={toggleOpenOptions}
|
||||
>
|
||||
Step: {textValue}
|
||||
Step: {isAutoStep ? `auto (${customStep})` : customStep}
|
||||
</Button>
|
||||
)}
|
||||
<Popper
|
||||
|
||||
@@ -19,11 +19,7 @@ import useBoolean from "../../../../hooks/useBoolean";
|
||||
import useWindowSize from "../../../../hooks/useWindowSize";
|
||||
import usePrevious from "../../../../hooks/usePrevious";
|
||||
|
||||
type Props = {
|
||||
onOpenSettings?: () => void;
|
||||
}
|
||||
|
||||
export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
|
||||
export const TimeSelector: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const { isDarkTheme } = useAppState();
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
@@ -57,7 +53,7 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
|
||||
setFrom(formatDateForNativeInput(dateFromSeconds(start)));
|
||||
}, [timezone, start]);
|
||||
|
||||
const setDuration = ({ duration, until, id }: { duration: string, until: Date, id: string }) => {
|
||||
const setDuration = ({ duration, until, id }: {duration: string, until: Date, id: string}) => {
|
||||
dispatch({ type: "SET_RELATIVE_TIME", payload: { duration, until, id } });
|
||||
handleCloseOptions();
|
||||
};
|
||||
@@ -79,23 +75,16 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
|
||||
|
||||
const setTimeAndClosePicker = () => {
|
||||
if (from && until) {
|
||||
dispatch({
|
||||
type: "SET_PERIOD", payload: {
|
||||
from: dayjs.tz(from).toDate(),
|
||||
to: dayjs.tz(until).toDate()
|
||||
}
|
||||
});
|
||||
dispatch({ type: "SET_PERIOD", payload: {
|
||||
from: dayjs.tz(from).toDate(),
|
||||
to: dayjs.tz(until).toDate()
|
||||
} });
|
||||
}
|
||||
handleCloseOptions();
|
||||
};
|
||||
|
||||
const onSwitchToNow = () => dispatch({ type: "RUN_QUERY_TO_NOW" });
|
||||
|
||||
const handleOpenSettings = () => {
|
||||
onOpenSettings && onOpenSettings();
|
||||
handleCloseOptions();
|
||||
};
|
||||
|
||||
const onCancelClick = () => {
|
||||
setUntil(formatDateForNativeInput(dateFromSeconds(end)));
|
||||
setFrom(formatDateForNativeInput(dateFromSeconds(start)));
|
||||
@@ -151,7 +140,6 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Popper
|
||||
open={openOptions}
|
||||
buttonRef={buttonRef}
|
||||
@@ -191,17 +179,13 @@ export const TimeSelector: FC<Props> = ({ onOpenSettings }) => {
|
||||
onEnter={setTimeAndClosePicker}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="vm-time-selector-left-timezone"
|
||||
onClick={handleOpenSettings}
|
||||
>
|
||||
<span className="vm-time-selector-left-timezone__title">{activeTimezone.region}</span>
|
||||
<span className="vm-time-selector-left-timezone__utc">{activeTimezone.utc}</span>
|
||||
</button>
|
||||
<div className="vm-time-selector-left-timezone">
|
||||
<div className="vm-time-selector-left-timezone__title">{activeTimezone.region}</div>
|
||||
<div className="vm-time-selector-left-timezone__utc">{activeTimezone.utc}</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="text"
|
||||
startIcon={<AlarmIcon/>}
|
||||
startIcon={<AlarmIcon />}
|
||||
onClick={onSwitchToNow}
|
||||
>
|
||||
switch to now
|
||||
|
||||
@@ -40,12 +40,8 @@
|
||||
gap: $padding-small;
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: $padding-small;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $color-primary;
|
||||
text-decoration: underline;
|
||||
}
|
||||
&__title {}
|
||||
|
||||
&__utc {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -633,14 +633,3 @@ export const DebugIcon = () => (
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const PlanetIcon = () => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2M4 12c0-.61.08-1.21.21-1.78L8.99 15v1c0 1.1.9 2 2 2v1.93C7.06 19.43 4 16.07 4 12m13.89 5.4c-.26-.81-1-1.4-1.9-1.4h-1v-3c0-.55-.45-1-1-1h-6v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41C17.92 5.77 20 8.65 20 12c0 2.08-.81 3.98-2.11 5.4"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
&_mobile {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
flex-grow: initial;
|
||||
|
||||
|
||||
@@ -6,11 +6,9 @@ import StepConfigurator from "../../components/Configurators/StepConfigurator/St
|
||||
import { TimeSelector } from "../../components/Configurators/TimeRangeSettings/TimeSelector/TimeSelector";
|
||||
import CardinalityDatePicker from "../../components/Configurators/CardinalityDatePicker/CardinalityDatePicker";
|
||||
import { ExecutionControls } from "../../components/Configurators/TimeRangeSettings/ExecutionControls/ExecutionControls";
|
||||
import GlobalSettings, { GlobalSettingsHandle } from "../../components/Configurators/GlobalSettings/GlobalSettings";
|
||||
import GlobalSettings from "../../components/Configurators/GlobalSettings/GlobalSettings";
|
||||
import ShortcutKeys from "../../components/Main/ShortcutKeys/ShortcutKeys";
|
||||
import { ControlsProps } from "../Header/HeaderControls/HeaderControls";
|
||||
import { useRef } from "react";
|
||||
import TimeZonePreview from "../../components/Configurators/GlobalSettings/TimeZonePreview/TimeZonePreview";
|
||||
|
||||
const ControlsMainLayout: FC<ControlsProps> = ({
|
||||
displaySidebar,
|
||||
@@ -19,7 +17,6 @@ const ControlsMainLayout: FC<ControlsProps> = ({
|
||||
accountIds,
|
||||
closeModal,
|
||||
}) => {
|
||||
const settingsRef = useRef<GlobalSettingsHandle>(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -30,15 +27,14 @@ const ControlsMainLayout: FC<ControlsProps> = ({
|
||||
>
|
||||
{headerSetup?.tenant && <TenantsConfiguration accountIds={accountIds || []}/>}
|
||||
{headerSetup?.stepControl && <StepConfigurator/>}
|
||||
{headerSetup?.timeSelector && <TimeSelector onOpenSettings={() => settingsRef.current?.open()}/>}
|
||||
{headerSetup?.timeSelector && <TimeSelector/>}
|
||||
{headerSetup?.cardinalityDatePicker && <CardinalityDatePicker/>}
|
||||
<TimeZonePreview onOpenSettings={() => settingsRef.current?.open()}/>
|
||||
{headerSetup?.executionControls && <ExecutionControls
|
||||
tooltip={headerSetup?.executionControls?.tooltip}
|
||||
useAutorefresh={headerSetup?.executionControls?.useAutorefresh}
|
||||
closeModal={closeModal}
|
||||
/>}
|
||||
<GlobalSettings ref={settingsRef}/>
|
||||
<GlobalSettings/>
|
||||
{!displaySidebar && <ShortcutKeys/>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-mobile-option {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: $padding-global;
|
||||
padding: $padding-global $padding-small;
|
||||
gap: $padding-small;
|
||||
padding: calc($padding-medium/2) 0;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
|
||||
@@ -18,33 +17,14 @@
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: $color-primary;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.1;
|
||||
background-color: currentColor;
|
||||
border-radius: $border-radius-medium;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 21px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transform: rotate(-90deg);
|
||||
color: $color-primary;
|
||||
}
|
||||
@@ -52,13 +32,11 @@
|
||||
&-text {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
gap: calc($padding-small / 2);
|
||||
gap: 2px;
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
|
||||
&__label {
|
||||
font-weight: 600;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__value {
|
||||
|
||||
@@ -39,6 +39,8 @@ VictoriaMetrics has the following prominent features:
|
||||
* Easy and fast backups from [instant snapshots](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282)
|
||||
can be done with [vmbackup](https://docs.victoriametrics.com/victoriametrics/vmbackup/) / [vmrestore](https://docs.victoriametrics.com/victoriametrics/vmrestore/) tools.
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
* It supports storage and retrieval of samples with timestamps that fall within the `[1970-01-02T00:00:00.000Z, 2262-03-31T23:59:59.999Z]` time range with millisecond precision.
|
||||
See [Retention](#retention) for details.
|
||||
* It implements a PromQL-like query language - [MetricsQL](https://docs.victoriametrics.com/victoriametrics/metricsql/), which provides improved functionality on top of PromQL.
|
||||
* It provides a global query view. Multiple Prometheus instances or any other data sources may ingest data into VictoriaMetrics. Later this data may be queried via a single query.
|
||||
* It provides high performance and good vertical and horizontal scalability for both
|
||||
@@ -1540,6 +1542,9 @@ It is safe to extend `-retentionPeriod` on existing data. If `-retentionPeriod`
|
||||
value than before, then data outside the configured period will be eventually deleted.
|
||||
|
||||
VictoriaMetrics does not support indefinite retention, but you can specify an arbitrarily high duration, e.g. `-retentionPeriod=100y`.
|
||||
Just keep in mind that VictoriaMetrics does not support samples with negative timestamps. Timestamps from `1970-01-01` are also not
|
||||
supported because this date has a special meaning internally. It therefore will reject samples with timestamps before
|
||||
`1970-01-02T00:00:00.000Z`.
|
||||
|
||||
By default, VictoriaMetrics doesn't accept samples with timestamps bigger than `now+2d`, e.g. 2 days in the future.
|
||||
If you need accepting samples with bigger timestamps, then specify the desired "future retention" via `-futureRetention` command-line flag.
|
||||
@@ -1551,6 +1556,9 @@ For example, the following command starts VictoriaMetrics, which accepts samples
|
||||
/path/to/victoria-metrics -futureRetention=1y
|
||||
```
|
||||
|
||||
VictoriaMetrics does not support stamples after `2262-03-31T23:59:59.999Z`. If the future retention includes dates after this timestamp,
|
||||
the samples for those dates will be rejected.
|
||||
|
||||
By default, VictoriaMetrics accepts samples with timestamps as old as the configured `-retentionPeriod` allows, e.g. it accepts backfilled
|
||||
historical data as long as it fits into the retention. If you need rejecting samples with historical timestamps older than the specified
|
||||
duration, then specify the desired duration via the `-maxBackfillAge` command-line flag. This can be useful for limiting ingestion of
|
||||
|
||||
@@ -42,7 +42,6 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
* FEATURE: [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): support [Prometheus native histograms](https://prometheus.io/docs/specs/native_histograms/) migration in [remote read mode](https://docs.victoriametrics.com/victoriametrics/vmctl/remoteread/). Native histograms are converted into `_count`, `_sum` and `_bucket` series with `vmrange` labels in the same way as VictoriaMetrics converts native histograms received via Prometheus remote write protocol, except that for native histograms with custom buckets the original bucket bounds are preserved instead of being estimated with the exponential formula. Previously native histograms were silently ignored in `SAMPLES` mode, while in stream mode the migration failed with `EOF` error. See [#11292](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11292). Thanks to @liuxu623 for contribution.
|
||||
* FEATURE: `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): enable [slowness-based re-routing](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#slowness-based-re-routing) by default. Previously, `-disableRerouting` defaulted to `true`, which limited ingestion throughput to the slowest `vmstorage` node. Now `-disableRerouting` defaults to `false`, so `vminsert` automatically routes data away from the slowest `vmstorage` node, improving overall ingestion performance. Slowness re-routing is automatically disabled when `-replicationFactor` is greater than 1. See [#11287](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11287).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): persist the selected auto-refresh interval in the URL. See [VictoriaLogs#1310](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1310).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): show the selected time zone UTC offset next to the date/time controls and allow opening time zone settings from it.
|
||||
|
||||
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/victoriametrics/metricsql/): properly drop data points filtered out by an inner [comparison operation](https://prometheus.io/docs/prometheus/latest/querying/operators/#comparison-binary-operators) when its result is used on the right side of another comparison. Previously, queries like `foo != (bar > 100)` could return unexpected results because filtered-out data points are represented internally as `NaN`, and `value != NaN` evaluates to `true`. Comparisons against explicitly present `NaN` values keep the previous behavior. See [#10018](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10018). Thanks to @zasdaym for contribution.
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): ignore HTTP proxy environment variables when scraping targets over Unix domain sockets. See [#11318](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11318). Thanks to @lwmacct for contribution.
|
||||
|
||||
@@ -1118,6 +1118,14 @@ func searchAndMerge[T any](qt *querytracer.Tracer, s *Storage, tr TimeRange, sea
|
||||
qt = qt.NewChild("search indexDBs: timeRange=%v", &tr)
|
||||
defer qt.Done()
|
||||
|
||||
var zeroValue T
|
||||
if tr.MinTimestamp < minUnixMilli {
|
||||
tr.MinTimestamp = minUnixMilli
|
||||
}
|
||||
if tr.MaxTimestamp < tr.MinTimestamp {
|
||||
return zeroValue, nil
|
||||
}
|
||||
|
||||
var idbts []indexDBWithType
|
||||
|
||||
ptws := s.tb.GetPartitions(tr)
|
||||
|
||||
@@ -402,6 +402,10 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
const numMonths = 10
|
||||
start := time.Date(1971, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
middle := start.AddDate(0, (numMonths-1)/2, 0)
|
||||
end := start.AddDate(0, numMonths-1, 0)
|
||||
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{})
|
||||
|
||||
var metricGroupName = []byte("metric")
|
||||
@@ -456,7 +460,7 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
assertCountMonthsWithLabels := func(count int) {
|
||||
t.Helper()
|
||||
|
||||
ts := time.Unix(0, 0)
|
||||
ts := start
|
||||
n := 0
|
||||
for range numMonths {
|
||||
lns, err := s.SearchLabelNames(nil, nil, TimeRange{ts.UnixMilli(), ts.UnixMilli()}, 1e5, 1e9, noDeadline)
|
||||
@@ -481,7 +485,7 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
var search Search
|
||||
defer search.MustClose()
|
||||
|
||||
search.Init(nil, s, []*TagFilters{tfs}, TimeRange{0, math.MaxInt64}, 1e5, noDeadline)
|
||||
search.Init(nil, s, []*TagFilters{tfs}, TimeRange{start.UnixMilli(), math.MaxInt64}, 1e5, noDeadline)
|
||||
n := 0
|
||||
for search.NextMetricBlock() {
|
||||
var b Block
|
||||
@@ -498,10 +502,6 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
// Verify no metrics exist
|
||||
assertCountRows(0)
|
||||
|
||||
start := time.Unix(0, 0)
|
||||
middle := start.AddDate(0, (numMonths-1)/2, 0)
|
||||
end := start.AddDate(0, numMonths-1, 0)
|
||||
|
||||
// Add some rows and flush, so next DeleteSeries() can delete them
|
||||
addRows(start, middle, false)
|
||||
s.DebugFlush()
|
||||
@@ -3385,53 +3385,190 @@ func TestStorageQueryWithoutIndex(t *testing.T) {
|
||||
testStorageSearchWithoutIndex(t, &opts)
|
||||
}
|
||||
|
||||
func TestStorageAddRows_SamplesWithZeroDate(t *testing.T) {
|
||||
func TestStorageAddRowsWithZeroDate(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
f := func(t *testing.T, disablePerDayIndex bool) {
|
||||
t.Helper()
|
||||
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{
|
||||
DisablePerDayIndex: disablePerDayIndex,
|
||||
})
|
||||
defer s.MustClose()
|
||||
|
||||
mn := MetricName{MetricGroup: []byte("metric")}
|
||||
mr := MetricRow{MetricNameRaw: mn.marshalRaw(nil)}
|
||||
for range 10 {
|
||||
mr.Timestamp = rand.Int63n(msecPerDay)
|
||||
mr.Value = float64(rand.Intn(1000))
|
||||
s.AddRows([]MetricRow{mr}, defaultPrecisionBits)
|
||||
s.DebugFlush()
|
||||
// Reset TSID cache so that insertion takes the path that involves
|
||||
// checking whether the index contains metricName->TSID mapping.
|
||||
s.resetAndSaveTSIDCache()
|
||||
}
|
||||
|
||||
want := 1
|
||||
firstUnixDay := TimeRange{
|
||||
MinTimestamp: 0,
|
||||
MaxTimestamp: msecPerDay - 1,
|
||||
}
|
||||
if got := s.newTimeseriesCreated.Load(); got != uint64(want) {
|
||||
t.Errorf("unexpected new timeseries count: got %d, want %d", got, want)
|
||||
}
|
||||
if got := testCountAllMetricNames(s, firstUnixDay); got != want {
|
||||
t.Errorf("unexpected metric name count: got %d, want %d", got, want)
|
||||
}
|
||||
if got := testCountAllMetricIDs(s, firstUnixDay); got != want {
|
||||
t.Errorf("unexpected metric id count: got %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
for _, disablePerDayIndex := range []bool{false, true} {
|
||||
name := fmt.Sprintf("disablePerDayIndex=%t", disablePerDayIndex)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
f(t, disablePerDayIndex)
|
||||
testStorageAddRowsWithZeroDate(t, disablePerDayIndex)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testStorageAddRowsWithZeroDate(t *testing.T, disablePerDayIndex bool) {
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{
|
||||
DisablePerDayIndex: disablePerDayIndex,
|
||||
})
|
||||
defer s.MustClose()
|
||||
|
||||
const numDays = 4
|
||||
var metricNamesAll []string
|
||||
labelNamesAll := []string{"__name__", "label"}
|
||||
var labelValuesAll []string
|
||||
mrs := make([]MetricRow, numDays)
|
||||
for day := range numDays {
|
||||
metricName := fmt.Sprintf("metric_%02d", day)
|
||||
labelName := fmt.Sprintf("label_%02d", day)
|
||||
labelValue := fmt.Sprintf("value_%02d", day)
|
||||
|
||||
if day != 0 {
|
||||
metricNamesAll = append(metricNamesAll, metricName)
|
||||
labelNamesAll = append(labelNamesAll, labelName)
|
||||
labelValuesAll = append(labelValuesAll, labelValue)
|
||||
}
|
||||
|
||||
mn := MetricName{
|
||||
MetricGroup: []byte(metricName),
|
||||
Tags: []Tag{
|
||||
{Key: []byte(labelName), Value: []byte("value")},
|
||||
{Key: []byte("label"), Value: []byte(labelValue)},
|
||||
},
|
||||
}
|
||||
mn.sortTags()
|
||||
|
||||
mrs[day].MetricNameRaw = mn.marshalRaw(nil)
|
||||
mrs[day].Timestamp = int64(day * msecPerDay)
|
||||
}
|
||||
|
||||
s.AddRows(mrs, defaultPrecisionBits)
|
||||
s.DebugFlush()
|
||||
if got, want := s.newTimeseriesCreated.Load(), uint64(numDays-1); got != want {
|
||||
t.Fatalf("unexpected new timeseries count: got %d, want %d", got, want)
|
||||
}
|
||||
if got, want := s.tooSmallTimestampRows.Load(), uint64(1); got != want {
|
||||
t.Fatalf("unexpected rows with too small timestamp: got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
assertMetricNames := func(tr TimeRange, want []string) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add(nil, []byte("metric_.*"), false, true); err != nil {
|
||||
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
|
||||
}
|
||||
got, err := s.SearchMetricNames(nil, []*TagFilters{tfs}, tr, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("SearchMetricNames(%v, %v) failed unexpectedly: %v", tfs, &tr, err)
|
||||
}
|
||||
for i, name := range got {
|
||||
var mn MetricName
|
||||
if err := mn.UnmarshalString(name); err != nil {
|
||||
t.Fatalf("Could not unmarshal metric name %q: %v", name, err)
|
||||
}
|
||||
got[i] = string(mn.MetricGroup)
|
||||
}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected metric names (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
assertLabelNames := func(tr TimeRange, want []string) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add(nil, []byte("metric_.*"), false, true); err != nil {
|
||||
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
|
||||
}
|
||||
got, err := s.SearchLabelNames(nil, []*TagFilters{tfs}, tr, 1e9, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("SearchLabelNames(%v, %v) failed unexpectedly: %s", tfs, &tr, err)
|
||||
}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected label names (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
assertLabelValues := func(tr TimeRange, want []string) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add([]byte("label"), []byte("value_.*"), false, true); err != nil {
|
||||
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
|
||||
}
|
||||
got, err := s.SearchLabelValues(nil, "label", []*TagFilters{tfs}, tr, 1e9, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("SearchLabelValues(%v, %v) failed unexpectedly: %s", tfs, tr, err)
|
||||
}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected label values (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
assertData := func(tr TimeRange, want []MetricRow) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add(nil, []byte("metric_.*"), false, true); err != nil {
|
||||
t.Fatalf("TagFilters.Add() failed unexpectedly: %v", err)
|
||||
}
|
||||
if err := testAssertSearchResult(s, tr, tfs, want); err != nil {
|
||||
t.Fatalf("Search(%v, %v) failed unexpectedly: %v", tfs, tr, err)
|
||||
}
|
||||
}
|
||||
|
||||
var tr TimeRange
|
||||
|
||||
// Empty time range.
|
||||
// Expect empty search results
|
||||
tr = TimeRange{}
|
||||
assertMetricNames(tr, nil)
|
||||
assertLabelNames(tr, []string{})
|
||||
assertLabelValues(tr, []string{})
|
||||
assertData(tr, nil)
|
||||
|
||||
// First day time range.
|
||||
// Expect empty search results
|
||||
tr = TimeRange{
|
||||
MinTimestamp: 0,
|
||||
MaxTimestamp: msecPerDay - 1,
|
||||
}
|
||||
assertMetricNames(tr, nil)
|
||||
assertLabelNames(tr, []string{})
|
||||
assertLabelValues(tr, []string{})
|
||||
assertData(tr, nil)
|
||||
|
||||
// Second day time range.
|
||||
tr = TimeRange{
|
||||
MinTimestamp: msecPerDay,
|
||||
MaxTimestamp: 2*msecPerDay - 1,
|
||||
}
|
||||
if disablePerDayIndex {
|
||||
// Expect index search results for all days if per-day index is
|
||||
// disabled.
|
||||
assertMetricNames(tr, metricNamesAll)
|
||||
assertLabelNames(tr, labelNamesAll)
|
||||
assertLabelValues(tr, labelValuesAll)
|
||||
} else {
|
||||
// Expect index search results on second day only if per-day index is
|
||||
// enabled.
|
||||
assertMetricNames(tr, []string{"metric_01"})
|
||||
assertLabelNames(tr, []string{"__name__", "label", "label_01"})
|
||||
assertLabelValues(tr, []string{"value_01"})
|
||||
}
|
||||
assertData(tr, mrs[1:2])
|
||||
|
||||
// First two days time range.
|
||||
// Expect results on second day only.
|
||||
tr = TimeRange{
|
||||
MinTimestamp: 0,
|
||||
MaxTimestamp: 2*msecPerDay - 1,
|
||||
}
|
||||
if disablePerDayIndex {
|
||||
// Expect index search results for all days if per-day index is
|
||||
// disabled.
|
||||
assertMetricNames(tr, metricNamesAll)
|
||||
assertLabelNames(tr, labelNamesAll)
|
||||
assertLabelValues(tr, labelValuesAll)
|
||||
} else {
|
||||
// Expect index search results on second day only if per-day index is
|
||||
// enabled.
|
||||
assertMetricNames(tr, []string{"metric_01"})
|
||||
assertLabelNames(tr, []string{"__name__", "label", "label_01"})
|
||||
assertLabelValues(tr, []string{"value_01"})
|
||||
}
|
||||
assertData(tr, mrs[1:2])
|
||||
}
|
||||
|
||||
// testSearchMetricIDs returns metricIDs for the given tfss and tr.
|
||||
//
|
||||
// The returned metricIDs are sorted. The function panics in in case of error.
|
||||
|
||||
@@ -429,9 +429,8 @@ func (tb *table) getMinMaxIngestionTimestamps() (int64, int64) {
|
||||
func (tb *table) getMinMaxTimestampsForAge(minAgeMsecs int64) (int64, int64) {
|
||||
now := int64(fasttime.UnixTimestamp() * 1000)
|
||||
minTimestamp := now - minAgeMsecs
|
||||
if minTimestamp < 0 {
|
||||
// Negative timestamps aren't supported by the storage.
|
||||
minTimestamp = 0
|
||||
if minTimestamp < minUnixMilli {
|
||||
minTimestamp = minUnixMilli
|
||||
}
|
||||
maxTimestamp := int64(maxUnixMilli)
|
||||
if maxUnixMilli-now > tb.s.futureRetentionMsecs {
|
||||
|
||||
@@ -40,12 +40,6 @@ type TimeRange struct {
|
||||
MaxTimestamp int64
|
||||
}
|
||||
|
||||
// Zero time range and zero date are used to force global index search.
|
||||
var (
|
||||
globalIndexTimeRange = TimeRange{}
|
||||
globalIndexDate = uint64(0)
|
||||
)
|
||||
|
||||
// DateRange returns the date range for the given time range.
|
||||
func (tr *TimeRange) DateRange() (uint64, uint64) {
|
||||
minDate := uint64(tr.MinTimestamp) / msecPerDay
|
||||
@@ -117,10 +111,29 @@ func (tr *TimeRange) contains(timestamp int64) bool {
|
||||
return tr.MinTimestamp <= timestamp && timestamp <= tr.MaxTimestamp
|
||||
}
|
||||
|
||||
// Zero time range and zero date are used to force global index search.
|
||||
var (
|
||||
globalIndexDate = uint64(0)
|
||||
globalIndexTimeRange = TimeRange{}
|
||||
)
|
||||
|
||||
const (
|
||||
msecPerDay = 24 * 3600 * 1000
|
||||
msecPerHour = 3600 * 1000
|
||||
|
||||
// minUnixMilli is the min millisecond that is allowed to be used as the
|
||||
// sample timestamp.
|
||||
//
|
||||
// It corresponds to the first millisecond of the second day of the Unix
|
||||
// Epoch, i.e. 1970-01-02T00:00:00.000Z.
|
||||
//
|
||||
// The first day of the Unix Epoch is reserved: zero date and zero time
|
||||
// range are used for indicating that the the global index search is
|
||||
// required. See globalIndexDate and globalIndexTimeRange above.
|
||||
//
|
||||
// Negative timestamps aren't supported.
|
||||
minUnixMilli = msecPerDay
|
||||
|
||||
// maxUnixMilli is the max millisecond that is allowed to be used as the
|
||||
// sample timestamp.
|
||||
//
|
||||
@@ -130,6 +143,6 @@ const (
|
||||
// time.UnixMicro(math.MaxInt64/1000) == 2262-04-11 23:47:16.854775 UTC.
|
||||
//
|
||||
// Round it to the last millisecond of the last complete partition:
|
||||
// 2262-03-31 23:59:59.999 UTC.
|
||||
// 2262-03-31T23:59:59.999Z.
|
||||
maxUnixMilli = 9222422399999
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user