Skip to content

Commit f71c56d

Browse files
authored
display max chunk age (#445)
1 parent 83a21e0 commit f71c56d

File tree

14 files changed

+130
-18
lines changed

14 files changed

+130
-18
lines changed

pkg/handler/loki.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,36 @@ func LokiConfig(cfg *loki.Config, param string) func(w http.ResponseWriter, r *h
272272
writeJSON(w, code, cfg[param])
273273
}
274274
}
275+
276+
func IngesterMaxChunkAge(cfg *loki.Config) func(w http.ResponseWriter, r *http.Request) {
277+
return func(w http.ResponseWriter, r *http.Request) {
278+
lokiClient := newLokiClient(cfg, r.Header, true)
279+
baseURL := strings.TrimRight(cfg.StatusURL.String(), "/")
280+
281+
resp, code, err := executeLokiQuery(fmt.Sprintf("%s/%s", baseURL, "config"), lokiClient)
282+
if err != nil {
283+
writeError(w, code, err.Error())
284+
return
285+
}
286+
287+
cfg := make(map[string]interface{})
288+
err = yaml.Unmarshal(resp, &cfg)
289+
if err != nil {
290+
hlog.WithError(err).Errorf("cannot unmarshal, response was: %v", string(resp))
291+
writeError(w, code, err.Error())
292+
return
293+
}
294+
295+
// default max chunk age is 2h
296+
// see https://grafana.com/docs/loki/latest/configure/#ingester
297+
var maxChunkAge interface{} = "2h"
298+
if cfg["ingester"] != nil {
299+
ingester := cfg["ingester"].(map[string]interface{})
300+
if ingester["max_chunk_age"] != nil {
301+
maxChunkAge = ingester["max_chunk_age"]
302+
}
303+
}
304+
305+
writeJSON(w, code, maxChunkAge)
306+
}
307+
}

pkg/server/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func setupRoutes(cfg *Config, authChecker auth.Checker) *mux.Router {
3333
api.HandleFunc("/loki/metrics", handler.LokiMetrics(&cfg.Loki))
3434
api.HandleFunc("/loki/buildinfo", handler.LokiBuildInfos(&cfg.Loki))
3535
api.HandleFunc("/loki/config/limits", handler.LokiConfig(&cfg.Loki, "limits_config"))
36+
api.HandleFunc("/loki/config/ingester/max_chunk_age", handler.IngesterMaxChunkAge(&cfg.Loki))
3637
api.HandleFunc("/loki/flow/records", handler.GetFlows(&cfg.Loki))
3738
api.HandleFunc("/loki/flow/metrics", handler.GetTopology(&cfg.Loki))
3839
api.HandleFunc("/loki/export", handler.ExportFlows(&cfg.Loki))

web/locales/en/plugin__netobserv-plugin.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@
215215
"Custom time range": "Custom time range",
216216
"Reset": "Reset",
217217
"Select a custom time range. Flows are selected based on their End Time value.": "Select a custom time range. Flows are selected based on their End Time value.",
218+
"Collection latency could be up to {{maxChunkAge}} corresponding to the current Loki \"max_chunk_age\" ingester configuration.": "Collection latency could be up to {{maxChunkAge}} corresponding to the current Loki \"max_chunk_age\" ingester configuration.",
218219
"Show all graphs": "Show all graphs",
219220
"Focus on this graph": "Focus on this graph",
220221
"No results found": "No results found",
@@ -344,6 +345,7 @@
344345
"in": "in",
345346
"Configuration": "Configuration",
346347
"Sampling": "Sampling",
348+
"Max chunk age": "Max chunk age",
347349
"Version": "Version",
348350
"Number": "Number",
349351
"Date": "Date",

web/package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"jsdom-global": "3.0.2",
7272
"mini-css-extract-plugin": "^2.6.0",
7373
"mobx": "^5.15.7",
74+
"parse-duration": "^1.1.0",
7475
"prettier": "^2.5.1",
7576
"pretty-quick": "^3.1.2",
7677
"react-transition-group": "^4.4.2",

web/src/api/routes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,12 @@ export const getLimits = (): Promise<unknown> => {
181181
return r.data;
182182
});
183183
};
184+
185+
export const getIngesterMaxChunkAge = (): Promise<string> => {
186+
return axios.get(ContextSingleton.getHost() + '/api/loki/config/ingester/max_chunk_age').then(r => {
187+
if (r.status >= 400) {
188+
throw new Error(`${r.statusText} [code=${r.status}]`);
189+
}
190+
return r.data;
191+
});
192+
};

web/src/components/modals/__tests__/time-range-modal.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe('<ColumnsModal />', () => {
1010
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1111
const fakeEvent: any = {};
1212
const props: TimeRangeModalProps = {
13+
maxChunkAge: NaN,
1314
isModalOpen: true,
1415
setModalOpen: jest.fn(),
1516
range: undefined,

web/src/components/modals/time-range-modal.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@
77
/* force time picker to overflow popup */
88
.time-range-row {
99
height: 33px;
10+
}
11+
12+
.date-time-pickers-container {
13+
margin: 1rem 0 2rem 0;
1014
}

web/src/components/modals/time-range-modal.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,26 @@ import {
1515
TextContent
1616
} from '@patternfly/react-core';
1717
import { TimeRange, toISODateString, twentyFourHourTime } from '../../utils/datetime';
18-
import { getDateMsInSeconds, getDateSInMiliseconds } from '../../utils/duration';
18+
import { formatDuration, getDateMsInSeconds, getDateSInMiliseconds } from '../../utils/duration';
1919
import './time-range-modal.css';
2020

2121
export interface TimeRangeModalProps {
22+
maxChunkAge: number;
2223
isModalOpen: boolean;
2324
setModalOpen: (v: boolean) => void;
2425
range?: TimeRange;
2526
setRange: (r: TimeRange) => void;
2627
id?: string;
2728
}
2829

29-
export const TimeRangeModal: React.FC<TimeRangeModalProps> = ({ id, isModalOpen, setModalOpen, range, setRange }) => {
30+
export const TimeRangeModal: React.FC<TimeRangeModalProps> = ({
31+
id,
32+
isModalOpen,
33+
setModalOpen,
34+
range,
35+
setRange,
36+
maxChunkAge
37+
}) => {
3038
const { t } = useTranslation('plugin__netobserv-plugin');
3139
const [error, setError] = React.useState<string | undefined>();
3240
const [fromDate, setFromDate] = React.useState<string | undefined>();
@@ -145,7 +153,7 @@ export const TimeRangeModal: React.FC<TimeRangeModalProps> = ({ id, isModalOpen,
145153
const getDateTimePickers = React.useCallback(() => {
146154
if (fromDate && fromTime && toDate && toTime) {
147155
return (
148-
<Flex direction={{ default: 'column' }}>
156+
<Flex className="date-time-pickers-container" direction={{ default: 'column' }}>
149157
<FlexItem>
150158
<Text component={TextVariants.h4}>{t('From')}</Text>
151159
<Flex direction={{ default: 'row' }} className="time-range-row">
@@ -271,6 +279,16 @@ export const TimeRangeModal: React.FC<TimeRangeModalProps> = ({ id, isModalOpen,
271279
</Text>
272280
</TextContent>
273281
{getDateTimePickers()}
282+
{!Number.isNaN(maxChunkAge) && (
283+
<TextContent>
284+
<Text component={TextVariants.blockquote}>
285+
{t(
286+
'Collection latency could be up to {{maxChunkAge}} corresponding to the current Loki "max_chunk_age" ingester configuration.',
287+
{ maxChunkAge: formatDuration(maxChunkAge) }
288+
)}
289+
</Text>
290+
</TextContent>
291+
)}
274292
</Modal>
275293
);
276294
};

web/src/components/netflow-traffic.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ import {
7474
} from '../model/topology';
7575
import { getFetchFunctions as getBackAndForthFetch } from '../utils/back-and-forth';
7676
import { Column, ColumnsId, ColumnSizeMap, getDefaultColumns } from '../utils/columns';
77-
import { loadConfig } from '../utils/config';
77+
import { loadConfig, loadMaxChunkAge } from '../utils/config';
7878
import { ContextSingleton } from '../utils/context';
7979
import { computeStepInterval, getTimeRangeOptions, TimeRange } from '../utils/datetime';
8080
import { formatDuration, getDateMsInSeconds, getDateSInMiliseconds, parseDuration } from '../utils/duration';
@@ -193,6 +193,7 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({ forcedFilters, i
193193
}
194194

195195
const [config, setConfig] = React.useState<Config>(defaultConfig);
196+
const [maxChunkAge, setMaxChunkAge] = React.useState<number>(NaN);
196197
const [warningMessage, setWarningMessage] = React.useState<string | undefined>();
197198
const [showViewOptions, setShowViewOptions] = useLocalStorage<boolean>(LOCAL_STORAGE_SHOW_OPTIONS_KEY, false);
198199
const [showHistogram, setShowHistogram] = useLocalStorage<boolean>(LOCAL_STORAGE_SHOW_HISTOGRAM_KEY, false);
@@ -251,7 +252,11 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({ forcedFilters, i
251252
const guidedTourRef = React.useRef<GuidedTourHandle>(null);
252253

253254
//use this ref to list any props / content loading state & events to skip tick function
254-
const initState = React.useRef<Array<'initDone' | 'configLoading' | 'configLoaded' | 'forcedFiltersLoaded'>>([]);
255+
const initState = React.useRef<
256+
Array<
257+
'initDone' | 'configLoading' | 'configLoaded' | 'maxChunkAgeLoading' | 'maxChunkAgeLoaded' | 'forcedFiltersLoaded'
258+
>
259+
>([]);
255260
const [panels, setSelectedPanels] = useLocalStorage<OverviewPanel[]>(
256261
LOCAL_STORAGE_OVERVIEW_IDS_KEY,
257262
getDefaultOverviewPanels(),
@@ -544,7 +549,9 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({ forcedFilters, i
544549

545550
const fetchTable = React.useCallback(
546551
(fq: FlowQuery) => {
547-
setMetrics({});
552+
if (!showHistogram) {
553+
setMetrics({});
554+
}
548555

549556
let currentMetrics = metricsRef.current;
550557
const { getRecords, getMetrics } = getFetchFunctions();
@@ -957,6 +964,15 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({ forcedFilters, i
957964
}
958965
}
959966

967+
// load max chunk age separately since it's a specific loki config
968+
if (!initState.current.includes('maxChunkAgeLoading')) {
969+
initState.current.push('maxChunkAgeLoading');
970+
loadMaxChunkAge().then(v => {
971+
initState.current.push('maxChunkAgeLoaded');
972+
setMaxChunkAge(v);
973+
});
974+
}
975+
960976
// init will trigger this useEffect update loop as soon as config is loaded
961977
return;
962978
}
@@ -1314,6 +1330,7 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({ forcedFilters, i
13141330
flows={flows}
13151331
metrics={metrics}
13161332
type={recordType}
1333+
maxChunkAge={maxChunkAge}
13171334
stats={stats}
13181335
limit={limit}
13191336
lastRefresh={lastRefresh}
@@ -1765,6 +1782,7 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({ forcedFilters, i
17651782
setModalOpen={setTRModalOpen}
17661783
range={typeof range === 'object' ? range : undefined}
17671784
setRange={setRange}
1785+
maxChunkAge={maxChunkAge}
17681786
/>
17691787
<OverviewPanelsModal
17701788
id="overview-panels-modal"

0 commit comments

Comments
 (0)