Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api: Add flag to set default step #3740

Merged
merged 12 commits into from
Feb 19, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ We use _breaking :warning:_ to mark changes that are not backward compatible (re

### Added

- [#3740](https://github.com/thanos-io/thanos/pull/3740) Query: Added `--query.default-step` flag to set default step.
- [#3700](https://github.com/thanos-io/thanos/pull/3700) ui: make old bucket viewer UI work with vanilla Prometheus blocks
- [#2641](https://github.com/thanos-io/thanos/issues/2641) Query Frontend: Added `--query-range.request-downsampled` flag enabling additional queries for downsampled data in case of empty or incomplete response to range request.
- [#3792](https://github.com/thanos-io/thanos/pull/3792) Receiver: Added `--tsdb.allow-overlapping-blocks` flag to allow overlapping tsdb blocks and enable vertical compaction
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ docs: ## Regenerates flags in docs for all thanos commands.
docs: $(EMBEDMD) build
@echo ">> generating docs"
@EMBEDMD_BIN="$(EMBEDMD)" SED_BIN="$(SED)" THANOS_BIN="$(GOBIN)/thanos" scripts/genflagdocs.sh
@echo ">> cleaning whte noise"
@echo ">> cleaning white noise"
@find . -type f -name "*.md" | SED_BIN="$(SED)" xargs scripts/cleanup-white-noise.sh

.PHONY: check-docs
Expand Down
6 changes: 6 additions & 0 deletions cmd/thanos/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ func registerQuery(app *extkingpin.App) {

defaultEvaluationInterval := extkingpin.ModelDuration(cmd.Flag("query.default-evaluation-interval", "Set default evaluation interval for sub queries.").Default("1m"))

defaultRangeQueryStep := extkingpin.ModelDuration(cmd.Flag("query.default-step", "Set default step for range queries. Default step is only used when step is not set in UI. In such cases, Thanos UI will use default step to calculate resolution (resolution = max(rangeSeconds / 250, defaultStep)). This will not work from Grafana, but Grafana has __step variable which can be used.").
Default("1s"))

storeResponseTimeout := extkingpin.ModelDuration(cmd.Flag("store.response-timeout", "If a Store doesn't send any data in this specified duration then a Store will be ignored and partial data will be returned if it's enabled. 0 disables timeout.").Default("0ms"))

cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, _ bool) error {
Expand Down Expand Up @@ -181,6 +184,7 @@ func registerQuery(app *extkingpin.App) {
*webPrefixHeaderName,
*maxConcurrentQueries,
*maxConcurrentSelects,
time.Duration(*defaultRangeQueryStep),
time.Duration(*queryTimeout),
*lookbackDelta,
*dynamicLookbackDelta,
Expand Down Expand Up @@ -231,6 +235,7 @@ func runQuery(
webPrefixHeaderName string,
maxConcurrentQueries int,
maxConcurrentSelects int,
defaultRangeQueryStep time.Duration,
queryTimeout time.Duration,
lookbackDelta time.Duration,
dynamicLookbackDelta bool,
Expand Down Expand Up @@ -454,6 +459,7 @@ func runQuery(
enableRulePartialResponse,
queryReplicaLabels,
flagsMap,
defaultRangeQueryStep,
instantDefaultMaxSourceResolution,
defaultMetadataTimeRange,
gate.New(
Expand Down
7 changes: 7 additions & 0 deletions docs/components/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,13 @@ Flags:
--query.default-evaluation-interval=1m
Set default evaluation interval for sub
queries.
--query.default-step=1s Set default step for range queries. Default
step is only used when step is not set in UI.
In such cases, Thanos UI will use default step
to calculate resolution (resolution =
max(rangeSeconds / 250, defaultStep)). This
will not work from Grafana, but Grafana has
__step variable which can be used.
--store.response-timeout=0ms
If a Store doesn't send any data in this
specified duration then a Store will be ignored
Expand Down
23 changes: 21 additions & 2 deletions pkg/api/query/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const (
ReplicaLabelsParam = "replicaLabels[]"
MatcherParam = "match[]"
StoreMatcherParam = "storeMatch[]"
Step = "step"
)

// QueryAPI is an API used by Thanos Querier.
Expand All @@ -77,6 +78,7 @@ type QueryAPI struct {
replicaLabels []string
storeSet *query.StoreSet

defaultRangeQueryStep time.Duration
defaultInstantQueryMaxSourceResolution time.Duration
defaultMetadataTimeRange time.Duration
}
Expand All @@ -93,6 +95,7 @@ func NewQueryAPI(
enableRulePartialResponse bool,
replicaLabels []string,
flagsMap map[string]string,
defaultRangeQueryStep time.Duration,
defaultInstantQueryMaxSourceResolution time.Duration,
defaultMetadataTimeRange time.Duration,
gate gate.Gate,
Expand All @@ -110,6 +113,7 @@ func NewQueryAPI(
enableRulePartialResponse: enableRulePartialResponse,
replicaLabels: replicaLabels,
storeSet: storeSet,
defaultRangeQueryStep: defaultRangeQueryStep,
defaultInstantQueryMaxSourceResolution: defaultInstantQueryMaxSourceResolution,
defaultMetadataTimeRange: defaultMetadataTimeRange,
}
Expand Down Expand Up @@ -225,6 +229,21 @@ func (qapi *QueryAPI) parsePartialResponseParam(r *http.Request, defaultEnablePa
return defaultEnablePartialResponse, nil
}

func (qapi *QueryAPI) parseStep(r *http.Request, defaultRangeQueryStep time.Duration, rangeSeconds int64) (time.Duration, *api.ApiError) {
// Overwrite the cli flag when provided as a query parameter.
if val := r.FormValue(Step); val != "" {
var err error
defaultRangeQueryStep, err = parseDuration(val)
if err != nil {
return 0, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrapf(err, "'%s' parameter", Step)}
}
}

// Default step is used this way to make it consistent with UI.
d := time.Duration(math.Max(float64(rangeSeconds/250), float64(defaultRangeQueryStep/time.Second))) * time.Second
return d, nil
}

func (qapi *QueryAPI) query(r *http.Request) (interface{}, []error, *api.ApiError) {
ts, err := parseTimeParam(r, "time", qapi.baseAPI.Now())
if err != nil {
Expand Down Expand Up @@ -320,9 +339,9 @@ func (qapi *QueryAPI) queryRange(r *http.Request) (interface{}, []error, *api.Ap
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
}

step, err := parseDuration(r.FormValue("step"))
step, apiErr := qapi.parseStep(r, qapi.defaultRangeQueryStep, int64(end.Sub(start)/time.Second))
if err != nil {
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Wrap(err, "param step")}
return nil, nil, apiErr
}

if step <= 0 {
Expand Down
26 changes: 20 additions & 6 deletions pkg/api/query/v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ func TestQueryEndpoints(t *testing.T) {
queryEngine: func(int64) *promql.Engine {
return qe
},
gate: gate.New(nil, 4),
gate: gate.New(nil, 4),
defaultRangeQueryStep: time.Second,
}

start := time.Unix(0, 0)
Expand Down Expand Up @@ -481,21 +482,34 @@ func TestQueryEndpoints(t *testing.T) {
},
},
},
// Missing query params in range queries.
// Use default step when missing.
{
endpoint: api.queryRange,
query: url.Values{
"query": []string{"time()"},
"start": []string{"0"},
"end": []string{"2"},
"step": []string{"1"},
},
errType: baseAPI.ErrorBadData,
response: &queryData{
ResultType: parser.ValueTypeMatrix,
Result: promql.Matrix{
promql.Series{
Points: []promql.Point{
{V: 0, T: timestamp.FromTime(start)},
{V: 1, T: timestamp.FromTime(start.Add(1 * time.Second))},
{V: 2, T: timestamp.FromTime(start.Add(2 * time.Second))},
},
Metric: nil,
},
},
},
},
// Missing query params in range queries.
{
endpoint: api.queryRange,
query: url.Values{
"query": []string{"time()"},
"start": []string{"0"},
"end": []string{"2"},
"step": []string{"1"},
},
errType: baseAPI.ErrorBadData,
Expand All @@ -505,7 +519,7 @@ func TestQueryEndpoints(t *testing.T) {
query: url.Values{
"query": []string{"time()"},
"start": []string{"0"},
"end": []string{"2"},
"step": []string{"1"},
},
errType: baseAPI.ErrorBadData,
},
Expand Down
240 changes: 120 additions & 120 deletions pkg/ui/bindata.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/ui/react-app/src/pages/flags/Flags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { withStatusIndicator } from '../../components/withStatusIndicator';
import { useFetch } from '../../hooks/useFetch';
import PathPrefixProps from '../../types/PathPrefixProps';

interface FlagMap {
export interface FlagMap {
[key: string]: string;
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/ui/react-app/src/pages/graph/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import QueryStatsView, { QueryStats } from './QueryStatsView';
import { Store } from '../../thanos/pages/stores/store';
import PathPrefixProps from '../../types/PathPrefixProps';
import { QueryParams } from '../../types/types';
import { parseRange } from '../../utils/index';

interface PanelProps {
id: string;
Expand All @@ -27,6 +28,7 @@ interface PanelProps {
onExecuteQuery: (query: string) => void;
stores: Store[];
enableAutocomplete: boolean;
defaultStep: string;
}

interface PanelState {
Expand Down Expand Up @@ -140,7 +142,9 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {

const endTime = this.getEndTime().valueOf() / 1000; // TODO: shouldn't valueof only work when it's a moment?
const startTime = endTime - this.props.options.range;
const resolution = this.props.options.resolution || Math.max(Math.floor(this.props.options.range / 250), 1);
const resolution =
this.props.options.resolution ||
Math.max(Math.floor(this.props.options.range / 250), parseRange(this.props.defaultStep) as number);
const params: URLSearchParams = new URLSearchParams({
query: expr,
dedup: this.props.options.useDeduplication.toString(),
Expand Down
17 changes: 16 additions & 1 deletion pkg/ui/react-app/src/pages/graph/PanelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Checkbox from '../../components/Checkbox';
import PathPrefixProps from '../../types/PathPrefixProps';
import { StoreListProps } from '../../thanos/pages/stores/Stores';
import { Store } from '../../thanos/pages/stores/store';
import { FlagMap } from '../flags/Flags';
import { generateID, decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString, callAll } from '../../utils';
import { useFetch } from '../../hooks/useFetch';
import { useLocalStorage } from '../../hooks/useLocalStorage';
Expand All @@ -26,6 +27,7 @@ interface PanelListProps extends PathPrefixProps, RouteComponentProps {
queryHistoryEnabled: boolean;
stores: StoreListProps;
enableAutocomplete: boolean;
defaultStep: string;
}

export const PanelListContent: FC<PanelListProps> = ({
Expand All @@ -35,6 +37,7 @@ export const PanelListContent: FC<PanelListProps> = ({
queryHistoryEnabled,
stores = {},
enableAutocomplete,
defaultStep,
...rest
}) => {
const [panels, setPanels] = useState(rest.panels);
Expand Down Expand Up @@ -117,6 +120,7 @@ export const PanelListContent: FC<PanelListProps> = ({
pathPrefix={pathPrefix}
stores={storeData}
enableAutocomplete={enableAutocomplete}
defaultStep={defaultStep}
/>
))}
<Button className="d-block mb-3" color="primary" onClick={addPanel}>
Expand All @@ -139,6 +143,10 @@ const PanelList: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = ''
const { response: storesRes, error: storesErr, isLoading: storesLoading } = useFetch<StoreListProps>(
`${pathPrefix}/api/v1/stores`
);
const { response: flagsRes, error: flagsErr, isLoading: flagsLoading } = useFetch<FlagMap>(
`${pathPrefix}/api/v1/status/flags`
);
const defaultStep = flagsRes?.data?.['query.default-step'] || '1s';

const browserTime = new Date().getTime() / 1000;
const { response: timeRes, error: timeErr } = useFetch<{ result: number[] }>(`${pathPrefix}/api/v1/query?query=time()`);
Expand Down Expand Up @@ -210,15 +218,22 @@ const PanelList: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = ''
Error fetching stores list: Unexpected response status when fetching stores: {storesErr.message}
</UncontrolledAlert>
)}
{flagsErr && (
<UncontrolledAlert color="danger">
<strong>Warning: </strong>
Error fetching flags list: Unexpected response status when fetching flags: {flagsErr.message}
</UncontrolledAlert>
)}
<PanelListContentWithIndicator
panels={decodePanelOptionsFromQueryString(window.location.search)}
pathPrefix={pathPrefix}
useLocalTime={useLocalTime}
metrics={metricsRes.data}
stores={debugMode ? storesRes.data : {}}
enableAutocomplete={enableAutocomplete}
defaultStep={defaultStep}
queryHistoryEnabled={enableQueryHistory}
isLoading={storesLoading}
isLoading={storesLoading || flagsLoading}
/>
</>
);
Expand Down
25 changes: 24 additions & 1 deletion pkg/ui/static/js/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Prometheus.Graph.prototype.initialize = function() {
self.options.max_source_resolution = "0s";
}

self.setDefaultStep(this);

// Draw graph controls and container from Handlebars template.

var options = {
Expand Down Expand Up @@ -295,6 +297,27 @@ Prometheus.Graph.prototype.initialize = function() {
}
};

Prometheus.Graph.prototype.setDefaultStep = function(el) {
var self = this;
$.ajax({
method: "GET",
url : PATH_PREFIX + "/api/v1/status/flags",
async: false,
dataType: "json",
success: function(json) {
if(json.status !== "success") {
self.showError("Error querying flags.");
return;
}
el.defaultStep = (json.data && "query.default-stp" in json.data) ? json.data["query.default-stp"] : "1s"

},
error: function() {
self.showError("Error loading flags.");
}
})
};

Prometheus.Graph.prototype.checkTimeDrift = function() {
var self = this;
var browserTime = new Date().getTime() / 1000;
Expand Down Expand Up @@ -545,7 +568,7 @@ Prometheus.Graph.prototype.submitQuery = function() {

var startTime = new Date().getTime();
var rangeSeconds = self.parseDuration(self.rangeInput.val());
var resolution = parseInt(self.queryForm.find("input[name=step_input]").val()) || Math.max(Math.floor(rangeSeconds / 250), 1);
var resolution = parseInt(self.queryForm.find("input[name=step_input]").val()) || Math.max(Math.floor(rangeSeconds / 250), self.parseDuration(self.defaultStep));
var maxSourceResolution = self.maxSourceResolutionInput.val()
var endDate = self.getEndDate() / 1000;
var moment = self.getMoment() / 1000;
Expand Down