mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-13 00:10:45 +01:00
Compare commits
7 Commits
fix/uplot-
...
infraM/rem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c26407ffe2 | ||
|
|
092e0b7d99 | ||
|
|
16849967c5 | ||
|
|
c8e7685f06 | ||
|
|
a3caaaf7f2 | ||
|
|
a355996a5d | ||
|
|
eea11972a9 |
@@ -183,7 +183,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
apiHandler.RegisterLogsRoutes(r, am)
|
||||
apiHandler.RegisterIntegrationRoutes(r, am)
|
||||
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
||||
apiHandler.RegisterInfraMetricsRoutes(r, am)
|
||||
apiHandler.RegisterQueryRangeV4Routes(r, am)
|
||||
apiHandler.RegisterWebSocketPaths(r, am)
|
||||
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export interface HostListPayload {
|
||||
filters: TagFilter;
|
||||
groupBy: BaseAutocompleteData[];
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
orderBy?: {
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null;
|
||||
start?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
export interface TimeSeriesValue {
|
||||
timestamp: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface TimeSeries {
|
||||
labels: Record<string, string>;
|
||||
labelsArray: Array<Record<string, string>>;
|
||||
values: TimeSeriesValue[];
|
||||
}
|
||||
|
||||
export interface HostData {
|
||||
hostName: string;
|
||||
active: boolean;
|
||||
os: string;
|
||||
/** Present when the list API returns grouped rows or extra resource attributes. */
|
||||
meta?: Record<string, string>;
|
||||
cpu: number;
|
||||
cpuTimeSeries: TimeSeries;
|
||||
memory: number;
|
||||
memoryTimeSeries: TimeSeries;
|
||||
wait: number;
|
||||
waitTimeSeries: TimeSeries;
|
||||
load15: number;
|
||||
load15TimeSeries: TimeSeries;
|
||||
}
|
||||
|
||||
export interface HostListResponse {
|
||||
status: string;
|
||||
data: {
|
||||
type: string;
|
||||
records: HostData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
endTimeBeforeRetention: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const getHostLists = async (
|
||||
props: HostListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<SuccessResponse<HostListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/hosts/list', props, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -10,6 +10,10 @@ jest.mock('providers/Timezone', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('providers/App/App', () => ({
|
||||
useAppContext: (): { featureFlags: [] } => ({ featureFlags: [] }),
|
||||
}));
|
||||
|
||||
const field = (name: string, type = ''): IField => ({
|
||||
name,
|
||||
type,
|
||||
|
||||
@@ -2,13 +2,15 @@ import type { ReactElement } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import {
|
||||
getBodyDisplayString,
|
||||
getSanitizedLogBody,
|
||||
} from 'container/LogDetailedView/utils';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { buildCompositeKey } from 'container/OptionsMenu/utils';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { getLogFieldValue } from 'lib/logs/flatLogData';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
@@ -26,6 +28,10 @@ export function useLogsTableColumns({
|
||||
fontSize,
|
||||
}: UseLogsTableColumnsProps): TableColumnDef<ILog>[] {
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
const { featureFlags } = useAppContext();
|
||||
const isBodyJsonEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.USE_JSON_BODY)
|
||||
?.active || false;
|
||||
|
||||
return useMemo<TableColumnDef<ILog>[]>(() => {
|
||||
const stateIndicatorCol: TableColumnDef<ILog> = {
|
||||
@@ -88,7 +94,8 @@ export function useLogsTableColumns({
|
||||
const makeUserFieldCol = (f: IField): TableColumnDef<ILog> => ({
|
||||
id: buildCompositeKey(f.name, f.type),
|
||||
header: f.name,
|
||||
accessorFn: (log): unknown => FlatLogData(log)[f.name],
|
||||
accessorFn: (log): unknown =>
|
||||
getLogFieldValue(log, f.name, isBodyJsonEnabled),
|
||||
enableRemove: true,
|
||||
width: { min: 192 },
|
||||
cell: ({ value }): ReactElement => (
|
||||
@@ -115,5 +122,5 @@ export function useLogsTableColumns({
|
||||
.filter((c): c is TableColumnDef<ILog> => c !== null);
|
||||
|
||||
return [stateIndicatorCol, ...fieldCols];
|
||||
}, [fields, fontSize, formatTimezoneAdjustedTimestamp]);
|
||||
}, [fields, fontSize, formatTimezoneAdjustedTimestamp, isBodyJsonEnabled]);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
dedupeOptionsByLabel,
|
||||
getFieldContextPrefix,
|
||||
getRecentOptions,
|
||||
isSupportedFunction,
|
||||
renderRecentDeleteButton,
|
||||
} from './utils';
|
||||
|
||||
@@ -1275,11 +1276,13 @@ function QuerySearch({
|
||||
}
|
||||
|
||||
if (queryContext.isInFunction) {
|
||||
options = Object.values(QUERY_BUILDER_FUNCTIONS).map((option) => ({
|
||||
label: option,
|
||||
apply: `${option}()`,
|
||||
type: 'function',
|
||||
}));
|
||||
options = Object.values(QUERY_BUILDER_FUNCTIONS)
|
||||
.filter((option) => isSupportedFunction(option, dataSource))
|
||||
.map((option) => ({
|
||||
label: option,
|
||||
apply: `${option}()`,
|
||||
type: 'function',
|
||||
}));
|
||||
|
||||
// Add space after selection for functions
|
||||
const optionsWithSpace = addSpaceToOptions(options);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { QUERY_BUILDER_FUNCTIONS } from 'constants/antlrQueryConstants';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
combineInitialAndUserExpression,
|
||||
dedupeOptionsByLabel,
|
||||
getFieldContextPrefix,
|
||||
getUserExpressionFromCombined,
|
||||
isSupportedFunction,
|
||||
} from '../utils';
|
||||
|
||||
describe('entityLogsExpression', () => {
|
||||
@@ -118,3 +122,19 @@ describe('dedupeOptionsByLabel', () => {
|
||||
expect(dedupeOptionsByLabel([])).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSupportedFunction', () => {
|
||||
const { HASANY, SEARCH } = QUERY_BUILDER_FUNCTIONS;
|
||||
|
||||
it('allows the has family on every signal', () => {
|
||||
[DataSource.LOGS, DataSource.TRACES, DataSource.METRICS].forEach((signal) => {
|
||||
expect(isSupportedFunction(HASANY, signal)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows search on logs only', () => {
|
||||
expect(isSupportedFunction(SEARCH, DataSource.LOGS)).toBe(true);
|
||||
expect(isSupportedFunction(SEARCH, DataSource.TRACES)).toBe(false);
|
||||
expect(isSupportedFunction(SEARCH, DataSource.METRICS)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { closeCompletion, startCompletion } from '@codemirror/autocomplete';
|
||||
import type { Completion } from '@codemirror/autocomplete';
|
||||
import type { EditorView } from '@uiw/react-codemirror';
|
||||
import { QUERY_BUILDER_FUNCTIONS } from 'constants/antlrQueryConstants';
|
||||
import dayjs from 'dayjs';
|
||||
import { normalizeFilterExpression } from 'lib/recentQueries/normalize';
|
||||
import * as recentQueriesStore from 'lib/recentQueries/recentQueriesStore';
|
||||
@@ -15,6 +16,15 @@ import {
|
||||
RECENTS_SECTION,
|
||||
} from './constants';
|
||||
|
||||
// search() lives in the logs condition builder only; traces and metrics reject it
|
||||
// as an unsupported operator. Every other function is implemented for all signals.
|
||||
export function isSupportedFunction(
|
||||
functionName: string,
|
||||
signal: SignalType,
|
||||
): boolean {
|
||||
return functionName !== QUERY_BUILDER_FUNCTIONS.SEARCH || signal === 'logs';
|
||||
}
|
||||
|
||||
export interface FieldContextPrefixMatch {
|
||||
context: string;
|
||||
remainder: string;
|
||||
|
||||
@@ -41,6 +41,7 @@ export const QUERY_BUILDER_FUNCTIONS = {
|
||||
HASANY: 'hasAny',
|
||||
HASALL: 'hasAll',
|
||||
HASTOKEN: 'hasToken',
|
||||
SEARCH: 'search',
|
||||
};
|
||||
|
||||
export function negateOperator(operatorOrFunction: string): string {
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface BaseConfigBuilderProps {
|
||||
panelType: PANEL_TYPES;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
useExactTimeRange?: boolean;
|
||||
stepInterval?: number;
|
||||
isLogScale?: boolean;
|
||||
yAxisUnit?: string;
|
||||
@@ -47,7 +46,6 @@ export function buildBaseConfig({
|
||||
thresholds,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
useExactTimeRange,
|
||||
stepInterval,
|
||||
isLogScale,
|
||||
yAxisUnit,
|
||||
@@ -90,7 +88,6 @@ export function buildBaseConfig({
|
||||
time: true,
|
||||
min: minTimeScale,
|
||||
max: maxTimeScale,
|
||||
useExactTimeRange,
|
||||
logBase: isLogScale ? 10 : undefined,
|
||||
distribution: isLogScale
|
||||
? DistributionType.Logarithmic
|
||||
|
||||
55
frontend/src/lib/logs/flatLogData.test.ts
Normal file
55
frontend/src/lib/logs/flatLogData.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { getLogFieldValue } from './flatLogData';
|
||||
|
||||
const asLog = (partial: Partial<ILog>): ILog => partial as unknown as ILog;
|
||||
|
||||
describe('getLogFieldValue', () => {
|
||||
it('resolves a nested body field by dotted key when use_json_body is on', () => {
|
||||
const log = asLog({ body: { a: { b: { c: 'deep' } } } });
|
||||
expect(getLogFieldValue(log, 'a.b.c', true)).toBe('deep');
|
||||
});
|
||||
|
||||
it('ignores body when use_json_body is off', () => {
|
||||
const log = asLog({ body: { a: { b: { c: 'deep' } } } });
|
||||
expect(getLogFieldValue(log, 'a.b.c', false)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ignores a stringified body even when use_json_body is on', () => {
|
||||
const log = asLog({ body: '{"a":{"b":1}}' });
|
||||
expect(getLogFieldValue(log, 'a.b', true)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('prefers the body value over attributes when the key exists in both (body first)', () => {
|
||||
const log = asLog({
|
||||
attributes_string: { 'a.b': 'attr' } as never,
|
||||
body: { a: { b: 'bodyval' } },
|
||||
});
|
||||
expect(getLogFieldValue(log, 'a.b', true)).toBe('bodyval');
|
||||
});
|
||||
|
||||
it('falls back to attributes when the key is not in the body', () => {
|
||||
const log = asLog({
|
||||
attributes_string: { 'x.y': 'attr' } as never,
|
||||
body: { other: 1 },
|
||||
});
|
||||
expect(getLogFieldValue(log, 'x.y', true)).toBe('attr');
|
||||
});
|
||||
|
||||
it('preserves falsy body values (0, false, empty string)', () => {
|
||||
const log = asLog({ body: { n: 0, flag: false, s: '' } });
|
||||
expect(getLogFieldValue(log, 'n', true)).toBe(0);
|
||||
expect(getLogFieldValue(log, 'flag', true)).toBe(false);
|
||||
expect(getLogFieldValue(log, 's', true)).toBe('');
|
||||
});
|
||||
|
||||
it('returns undefined when the body path is missing', () => {
|
||||
const log = asLog({ body: { x: 1 } });
|
||||
expect(getLogFieldValue(log, 'nope', true)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined when a mid path segment is not an object', () => {
|
||||
const log = asLog({ body: { a: { b: 'leaf' } } });
|
||||
expect(getLogFieldValue(log, 'a.b.c', true)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defaultTo } from 'lodash-es';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { ILog, ILogBody } from 'types/api/logs/log';
|
||||
|
||||
export function FlatLogData(log: ILog): Record<string, string> {
|
||||
const flattenLogObject: Record<string, string> = {};
|
||||
@@ -15,3 +15,29 @@ export function FlatLogData(log: ILog): Record<string, string> {
|
||||
});
|
||||
return flattenLogObject;
|
||||
}
|
||||
|
||||
function getBodyFieldValue(body: ILogBody, key: string): unknown {
|
||||
return key.split('.').reduce<unknown>((acc, segment) => {
|
||||
if (acc && typeof acc === 'object' && !Array.isArray(acc)) {
|
||||
return (acc as Record<string, unknown>)[segment];
|
||||
}
|
||||
return undefined;
|
||||
}, body);
|
||||
}
|
||||
|
||||
// Resolve one field for the logs table. A JSON body is checked first (use_json_body
|
||||
// only), splitting the key on `.`; otherwise fall back to FlatLogData
|
||||
// (attributes/resources/scope/top-level).
|
||||
export function getLogFieldValue(
|
||||
log: ILog,
|
||||
fieldName: string,
|
||||
isBodyJsonEnabled: boolean,
|
||||
): unknown {
|
||||
if (isBodyJsonEnabled && log.body && typeof log.body === 'object') {
|
||||
const bodyValue = getBodyFieldValue(log.body, fieldName);
|
||||
if (bodyValue !== undefined) {
|
||||
return bodyValue;
|
||||
}
|
||||
}
|
||||
return FlatLogData(log)[fieldName];
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('normalizeFilterExpression', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('lowercases HAS / HASANY / HASALL / HASTOKEN function names', () => {
|
||||
it('lowercases HAS / HASANY / HASALL / HASTOKEN / SEARCH function names', () => {
|
||||
expect(normalizeFilterExpression('HAS(tags, "x")')).toBe(
|
||||
normalizeFilterExpression('has(tags, "x")'),
|
||||
);
|
||||
@@ -47,6 +47,9 @@ describe('normalizeFilterExpression', () => {
|
||||
expect(normalizeFilterExpression('HASTOKEN(msg, "err")')).toBe(
|
||||
normalizeFilterExpression('hasToken(msg, "err")'),
|
||||
);
|
||||
expect(normalizeFilterExpression('SEARCH("err")')).toBe(
|
||||
normalizeFilterExpression('search("err")'),
|
||||
);
|
||||
});
|
||||
|
||||
it('lowercases TRUE / FALSE boolean literals', () => {
|
||||
|
||||
@@ -42,7 +42,6 @@ export class UPlotScaleBuilder extends ConfigBuilder<
|
||||
logBase = 10,
|
||||
padMinBy = 0,
|
||||
padMaxBy = 0.05,
|
||||
useExactTimeRange = false,
|
||||
} = this.props;
|
||||
|
||||
// Special handling for time scales (X axis)
|
||||
@@ -59,20 +58,14 @@ export class UPlotScaleBuilder extends ConfigBuilder<
|
||||
|
||||
// Align max time to "endTime - 1 minute", rounded down to minute precision
|
||||
// This matches legacy getXAxisScale behavior and avoids empty space at the right edge
|
||||
if (!useExactTimeRange) {
|
||||
const oneMinuteAgoTimestamp = (maxTime - 60) * 1000;
|
||||
const currentDate = new Date(oneMinuteAgoTimestamp);
|
||||
const oneMinuteAgoTimestamp = (maxTime - 60) * 1000;
|
||||
const currentDate = new Date(oneMinuteAgoTimestamp);
|
||||
|
||||
currentDate.setSeconds(0);
|
||||
currentDate.setMilliseconds(0);
|
||||
currentDate.setSeconds(0);
|
||||
currentDate.setMilliseconds(0);
|
||||
|
||||
const unixTimestampSeconds = Math.floor(currentDate.getTime() / 1000);
|
||||
|
||||
// Trimming past min inverts the range, which uPlot draws as an empty plot.
|
||||
if (unixTimestampSeconds > minTime) {
|
||||
maxTime = unixTimestampSeconds;
|
||||
}
|
||||
}
|
||||
const unixTimestampSeconds = Math.floor(currentDate.getTime() / 1000);
|
||||
maxTime = unixTimestampSeconds;
|
||||
|
||||
return {
|
||||
[scaleKey]: {
|
||||
|
||||
@@ -79,44 +79,6 @@ describe('UPlotScaleBuilder', () => {
|
||||
expect(resolvedMax).toBe(expectedMax);
|
||||
});
|
||||
|
||||
it('plots min/max as given when useExactTimeRange is set', () => {
|
||||
const min = 1_700_000_000;
|
||||
const max = 1_700_000_630;
|
||||
|
||||
const builder = new UPlotScaleBuilder(
|
||||
createScaleProps({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min,
|
||||
max,
|
||||
useExactTimeRange: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const config = builder.getConfig();
|
||||
|
||||
expect(config.x.range).toStrictEqual([min, max]);
|
||||
});
|
||||
|
||||
it('keeps the requested end when the window is shorter than the trim', () => {
|
||||
// 23 second window: trimming a minute off the end would put max before min.
|
||||
const min = 1_786_527_160;
|
||||
const max = 1_786_527_183;
|
||||
|
||||
const builder = new UPlotScaleBuilder(
|
||||
createScaleProps({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min,
|
||||
max,
|
||||
}),
|
||||
);
|
||||
|
||||
const config = builder.getConfig();
|
||||
|
||||
expect(config.x.range).toStrictEqual([min, max]);
|
||||
});
|
||||
|
||||
it('falls back to getFallbackMinMaxTimeStamp when time scale has no min/max', () => {
|
||||
getFallbackMinMaxSpy.mockReturnValue({
|
||||
fallbackMin: 100,
|
||||
|
||||
@@ -97,8 +97,6 @@ export interface ScaleProps {
|
||||
auto?: boolean;
|
||||
logBase?: uPlot.Scale.LogBase;
|
||||
distribution?: DistributionType;
|
||||
/** Plots a time scale's `min`/`max` as given, skipping the trim below. */
|
||||
useExactTimeRange?: boolean;
|
||||
}
|
||||
|
||||
export enum DisconnectedValuesMode {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,12 +24,13 @@ HASTOKEN=23
|
||||
HAS=24
|
||||
HASANY=25
|
||||
HASALL=26
|
||||
BOOL=27
|
||||
NUMBER=28
|
||||
QUOTED_TEXT=29
|
||||
KEY=30
|
||||
WS=31
|
||||
FREETEXT=32
|
||||
SEARCH=27
|
||||
BOOL=28
|
||||
NUMBER=29
|
||||
QUOTED_TEXT=30
|
||||
KEY=31
|
||||
WS=32
|
||||
FREETEXT=33
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,12 +24,13 @@ HASTOKEN=23
|
||||
HAS=24
|
||||
HASANY=25
|
||||
HASALL=26
|
||||
BOOL=27
|
||||
NUMBER=28
|
||||
QUOTED_TEXT=29
|
||||
KEY=30
|
||||
WS=31
|
||||
FREETEXT=32
|
||||
SEARCH=27
|
||||
BOOL=28
|
||||
NUMBER=29
|
||||
QUOTED_TEXT=30
|
||||
KEY=31
|
||||
WS=32
|
||||
FREETEXT=33
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.2
|
||||
// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols
|
||||
import {
|
||||
ATN,
|
||||
@@ -38,12 +38,13 @@ export default class FilterQueryLexer extends Lexer {
|
||||
public static readonly HAS = 24;
|
||||
public static readonly HASANY = 25;
|
||||
public static readonly HASALL = 26;
|
||||
public static readonly BOOL = 27;
|
||||
public static readonly NUMBER = 28;
|
||||
public static readonly QUOTED_TEXT = 29;
|
||||
public static readonly KEY = 30;
|
||||
public static readonly WS = 31;
|
||||
public static readonly FREETEXT = 32;
|
||||
public static readonly SEARCH = 27;
|
||||
public static readonly BOOL = 28;
|
||||
public static readonly NUMBER = 29;
|
||||
public static readonly QUOTED_TEXT = 30;
|
||||
public static readonly KEY = 31;
|
||||
public static readonly WS = 32;
|
||||
public static readonly FREETEXT = 33;
|
||||
public static readonly EOF = Token.EOF;
|
||||
|
||||
public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ];
|
||||
@@ -68,8 +69,9 @@ export default class FilterQueryLexer extends Lexer {
|
||||
"AND", "OR",
|
||||
"HASTOKEN",
|
||||
"HAS", "HASANY",
|
||||
"HASALL", "BOOL",
|
||||
"NUMBER", "QUOTED_TEXT",
|
||||
"HASALL", "SEARCH",
|
||||
"BOOL", "NUMBER",
|
||||
"QUOTED_TEXT",
|
||||
"KEY", "WS",
|
||||
"FREETEXT" ];
|
||||
public static readonly modeNames: string[] = [ "DEFAULT_MODE", ];
|
||||
@@ -78,8 +80,8 @@ export default class FilterQueryLexer extends Lexer {
|
||||
"LPAREN", "RPAREN", "LBRACK", "RBRACK", "COMMA", "EQUALS", "NOT_EQUALS",
|
||||
"NEQ", "LT", "LE", "GT", "GE", "LIKE", "ILIKE", "BETWEEN", "EXISTS", "REGEXP",
|
||||
"CONTAINS", "IN", "NOT", "AND", "OR", "HASTOKEN", "HAS", "HASANY", "HASALL",
|
||||
"BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT", "EMPTY_BRACKS", "OLD_JSON_BRACKS",
|
||||
"KEY", "WS", "DIGIT", "FREETEXT",
|
||||
"SEARCH", "BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT", "EMPTY_BRACKS",
|
||||
"OLD_JSON_BRACKS", "KEY", "WS", "DIGIT", "FREETEXT",
|
||||
];
|
||||
|
||||
|
||||
@@ -100,119 +102,122 @@ export default class FilterQueryLexer extends Lexer {
|
||||
|
||||
public get modeNames(): string[] { return FilterQueryLexer.modeNames; }
|
||||
|
||||
public static readonly _serializedATN: number[] = [4,0,32,320,6,-1,2,0,
|
||||
public static readonly _serializedATN: number[] = [4,0,33,329,6,-1,2,0,
|
||||
7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,
|
||||
7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,
|
||||
16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,
|
||||
2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,
|
||||
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,1,0,1,0,1,1,1,
|
||||
1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,89,8,5,1,6,1,6,1,6,1,7,1,7,1,
|
||||
7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,
|
||||
1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,
|
||||
15,1,15,1,15,1,15,1,15,1,15,3,15,132,8,15,1,16,1,16,1,16,1,16,1,16,1,16,
|
||||
1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,149,8,17,1,18,1,18,1,
|
||||
18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,1,22,1,22,
|
||||
1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,
|
||||
24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,
|
||||
1,26,1,26,1,26,1,26,3,26,201,8,26,1,27,1,27,1,28,3,28,206,8,28,1,28,4,28,
|
||||
209,8,28,11,28,12,28,210,1,28,1,28,5,28,215,8,28,10,28,12,28,218,9,28,3,
|
||||
28,220,8,28,1,28,1,28,3,28,224,8,28,1,28,4,28,227,8,28,11,28,12,28,228,
|
||||
3,28,231,8,28,1,28,3,28,234,8,28,1,28,1,28,4,28,238,8,28,11,28,12,28,239,
|
||||
1,28,1,28,3,28,244,8,28,1,28,4,28,247,8,28,11,28,12,28,248,3,28,251,8,28,
|
||||
3,28,253,8,28,1,29,1,29,1,29,1,29,5,29,259,8,29,10,29,12,29,262,9,29,1,
|
||||
29,1,29,1,29,1,29,1,29,5,29,269,8,29,10,29,12,29,272,9,29,1,29,3,29,275,
|
||||
8,29,1,30,1,30,5,30,279,8,30,10,30,12,30,282,9,30,1,31,1,31,1,31,1,32,1,
|
||||
32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,4,33,298,8,33,11,33,12,
|
||||
33,299,5,33,302,8,33,10,33,12,33,305,9,33,1,34,4,34,308,8,34,11,34,12,34,
|
||||
309,1,34,1,34,1,35,1,35,1,36,4,36,317,8,36,11,36,12,36,318,0,0,37,1,1,3,
|
||||
2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,
|
||||
16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,
|
||||
0,57,28,59,29,61,0,63,0,65,0,67,30,69,31,71,0,73,32,1,0,29,2,0,76,76,108,
|
||||
108,2,0,73,73,105,105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98,
|
||||
98,2,0,84,84,116,116,2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120,
|
||||
120,2,0,83,83,115,115,2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112,
|
||||
112,2,0,67,67,99,99,2,0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100,
|
||||
2,0,72,72,104,104,2,0,89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102,
|
||||
2,0,43,43,45,45,2,0,34,34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97,
|
||||
123,7,0,35,36,45,45,47,58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32,
|
||||
32,1,0,48,57,8,0,9,10,13,13,32,34,39,41,44,44,60,62,91,91,93,93,344,0,1,
|
||||
1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,
|
||||
13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,
|
||||
0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,
|
||||
35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,
|
||||
0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,57,1,0,0,0,0,
|
||||
59,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,73,1,0,0,0,1,75,1,0,0,0,3,77,1,0,
|
||||
0,0,5,79,1,0,0,0,7,81,1,0,0,0,9,83,1,0,0,0,11,88,1,0,0,0,13,90,1,0,0,0,
|
||||
15,93,1,0,0,0,17,96,1,0,0,0,19,98,1,0,0,0,21,101,1,0,0,0,23,103,1,0,0,0,
|
||||
25,106,1,0,0,0,27,111,1,0,0,0,29,117,1,0,0,0,31,125,1,0,0,0,33,133,1,0,
|
||||
0,0,35,140,1,0,0,0,37,150,1,0,0,0,39,153,1,0,0,0,41,157,1,0,0,0,43,161,
|
||||
1,0,0,0,45,164,1,0,0,0,47,173,1,0,0,0,49,177,1,0,0,0,51,184,1,0,0,0,53,
|
||||
200,1,0,0,0,55,202,1,0,0,0,57,252,1,0,0,0,59,274,1,0,0,0,61,276,1,0,0,0,
|
||||
63,283,1,0,0,0,65,286,1,0,0,0,67,290,1,0,0,0,69,307,1,0,0,0,71,313,1,0,
|
||||
0,0,73,316,1,0,0,0,75,76,5,40,0,0,76,2,1,0,0,0,77,78,5,41,0,0,78,4,1,0,
|
||||
0,0,79,80,5,91,0,0,80,6,1,0,0,0,81,82,5,93,0,0,82,8,1,0,0,0,83,84,5,44,
|
||||
0,0,84,10,1,0,0,0,85,89,5,61,0,0,86,87,5,61,0,0,87,89,5,61,0,0,88,85,1,
|
||||
0,0,0,88,86,1,0,0,0,89,12,1,0,0,0,90,91,5,33,0,0,91,92,5,61,0,0,92,14,1,
|
||||
0,0,0,93,94,5,60,0,0,94,95,5,62,0,0,95,16,1,0,0,0,96,97,5,60,0,0,97,18,
|
||||
1,0,0,0,98,99,5,60,0,0,99,100,5,61,0,0,100,20,1,0,0,0,101,102,5,62,0,0,
|
||||
102,22,1,0,0,0,103,104,5,62,0,0,104,105,5,61,0,0,105,24,1,0,0,0,106,107,
|
||||
7,0,0,0,107,108,7,1,0,0,108,109,7,2,0,0,109,110,7,3,0,0,110,26,1,0,0,0,
|
||||
111,112,7,1,0,0,112,113,7,0,0,0,113,114,7,1,0,0,114,115,7,2,0,0,115,116,
|
||||
7,3,0,0,116,28,1,0,0,0,117,118,7,4,0,0,118,119,7,3,0,0,119,120,7,5,0,0,
|
||||
120,121,7,6,0,0,121,122,7,3,0,0,122,123,7,3,0,0,123,124,7,7,0,0,124,30,
|
||||
1,0,0,0,125,126,7,3,0,0,126,127,7,8,0,0,127,128,7,1,0,0,128,129,7,9,0,0,
|
||||
129,131,7,5,0,0,130,132,7,9,0,0,131,130,1,0,0,0,131,132,1,0,0,0,132,32,
|
||||
1,0,0,0,133,134,7,10,0,0,134,135,7,3,0,0,135,136,7,11,0,0,136,137,7,3,0,
|
||||
0,137,138,7,8,0,0,138,139,7,12,0,0,139,34,1,0,0,0,140,141,7,13,0,0,141,
|
||||
142,7,14,0,0,142,143,7,7,0,0,143,144,7,5,0,0,144,145,7,15,0,0,145,146,7,
|
||||
1,0,0,146,148,7,7,0,0,147,149,7,9,0,0,148,147,1,0,0,0,148,149,1,0,0,0,149,
|
||||
36,1,0,0,0,150,151,7,1,0,0,151,152,7,7,0,0,152,38,1,0,0,0,153,154,7,7,0,
|
||||
0,154,155,7,14,0,0,155,156,7,5,0,0,156,40,1,0,0,0,157,158,7,15,0,0,158,
|
||||
159,7,7,0,0,159,160,7,16,0,0,160,42,1,0,0,0,161,162,7,14,0,0,162,163,7,
|
||||
10,0,0,163,44,1,0,0,0,164,165,7,17,0,0,165,166,7,15,0,0,166,167,7,9,0,0,
|
||||
167,168,7,5,0,0,168,169,7,14,0,0,169,170,7,2,0,0,170,171,7,3,0,0,171,172,
|
||||
7,7,0,0,172,46,1,0,0,0,173,174,7,17,0,0,174,175,7,15,0,0,175,176,7,9,0,
|
||||
0,176,48,1,0,0,0,177,178,7,17,0,0,178,179,7,15,0,0,179,180,7,9,0,0,180,
|
||||
181,7,15,0,0,181,182,7,7,0,0,182,183,7,18,0,0,183,50,1,0,0,0,184,185,7,
|
||||
17,0,0,185,186,7,15,0,0,186,187,7,9,0,0,187,188,7,15,0,0,188,189,7,0,0,
|
||||
0,189,190,7,0,0,0,190,52,1,0,0,0,191,192,7,5,0,0,192,193,7,10,0,0,193,194,
|
||||
7,19,0,0,194,201,7,3,0,0,195,196,7,20,0,0,196,197,7,15,0,0,197,198,7,0,
|
||||
0,0,198,199,7,9,0,0,199,201,7,3,0,0,200,191,1,0,0,0,200,195,1,0,0,0,201,
|
||||
54,1,0,0,0,202,203,7,21,0,0,203,56,1,0,0,0,204,206,3,55,27,0,205,204,1,
|
||||
0,0,0,205,206,1,0,0,0,206,208,1,0,0,0,207,209,3,71,35,0,208,207,1,0,0,0,
|
||||
209,210,1,0,0,0,210,208,1,0,0,0,210,211,1,0,0,0,211,219,1,0,0,0,212,216,
|
||||
5,46,0,0,213,215,3,71,35,0,214,213,1,0,0,0,215,218,1,0,0,0,216,214,1,0,
|
||||
0,0,216,217,1,0,0,0,217,220,1,0,0,0,218,216,1,0,0,0,219,212,1,0,0,0,219,
|
||||
220,1,0,0,0,220,230,1,0,0,0,221,223,7,3,0,0,222,224,3,55,27,0,223,222,1,
|
||||
0,0,0,223,224,1,0,0,0,224,226,1,0,0,0,225,227,3,71,35,0,226,225,1,0,0,0,
|
||||
227,228,1,0,0,0,228,226,1,0,0,0,228,229,1,0,0,0,229,231,1,0,0,0,230,221,
|
||||
1,0,0,0,230,231,1,0,0,0,231,253,1,0,0,0,232,234,3,55,27,0,233,232,1,0,0,
|
||||
0,233,234,1,0,0,0,234,235,1,0,0,0,235,237,5,46,0,0,236,238,3,71,35,0,237,
|
||||
236,1,0,0,0,238,239,1,0,0,0,239,237,1,0,0,0,239,240,1,0,0,0,240,250,1,0,
|
||||
0,0,241,243,7,3,0,0,242,244,3,55,27,0,243,242,1,0,0,0,243,244,1,0,0,0,244,
|
||||
246,1,0,0,0,245,247,3,71,35,0,246,245,1,0,0,0,247,248,1,0,0,0,248,246,1,
|
||||
0,0,0,248,249,1,0,0,0,249,251,1,0,0,0,250,241,1,0,0,0,250,251,1,0,0,0,251,
|
||||
253,1,0,0,0,252,205,1,0,0,0,252,233,1,0,0,0,253,58,1,0,0,0,254,260,5,34,
|
||||
0,0,255,259,8,22,0,0,256,257,5,92,0,0,257,259,9,0,0,0,258,255,1,0,0,0,258,
|
||||
256,1,0,0,0,259,262,1,0,0,0,260,258,1,0,0,0,260,261,1,0,0,0,261,263,1,0,
|
||||
0,0,262,260,1,0,0,0,263,275,5,34,0,0,264,270,5,39,0,0,265,269,8,23,0,0,
|
||||
266,267,5,92,0,0,267,269,9,0,0,0,268,265,1,0,0,0,268,266,1,0,0,0,269,272,
|
||||
1,0,0,0,270,268,1,0,0,0,270,271,1,0,0,0,271,273,1,0,0,0,272,270,1,0,0,0,
|
||||
273,275,5,39,0,0,274,254,1,0,0,0,274,264,1,0,0,0,275,60,1,0,0,0,276,280,
|
||||
7,24,0,0,277,279,7,25,0,0,278,277,1,0,0,0,279,282,1,0,0,0,280,278,1,0,0,
|
||||
0,280,281,1,0,0,0,281,62,1,0,0,0,282,280,1,0,0,0,283,284,5,91,0,0,284,285,
|
||||
5,93,0,0,285,64,1,0,0,0,286,287,5,91,0,0,287,288,5,42,0,0,288,289,5,93,
|
||||
0,0,289,66,1,0,0,0,290,303,3,61,30,0,291,292,5,46,0,0,292,302,3,61,30,0,
|
||||
293,302,3,63,31,0,294,302,3,65,32,0,295,297,5,46,0,0,296,298,3,71,35,0,
|
||||
297,296,1,0,0,0,298,299,1,0,0,0,299,297,1,0,0,0,299,300,1,0,0,0,300,302,
|
||||
1,0,0,0,301,291,1,0,0,0,301,293,1,0,0,0,301,294,1,0,0,0,301,295,1,0,0,0,
|
||||
302,305,1,0,0,0,303,301,1,0,0,0,303,304,1,0,0,0,304,68,1,0,0,0,305,303,
|
||||
1,0,0,0,306,308,7,26,0,0,307,306,1,0,0,0,308,309,1,0,0,0,309,307,1,0,0,
|
||||
0,309,310,1,0,0,0,310,311,1,0,0,0,311,312,6,34,0,0,312,70,1,0,0,0,313,314,
|
||||
7,27,0,0,314,72,1,0,0,0,315,317,8,28,0,0,316,315,1,0,0,0,317,318,1,0,0,
|
||||
0,318,316,1,0,0,0,318,319,1,0,0,0,319,74,1,0,0,0,29,0,88,131,148,200,205,
|
||||
210,216,219,223,228,230,233,239,243,248,250,252,258,260,268,270,274,280,
|
||||
299,301,303,309,318,1,6,0,0];
|
||||
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,1,0,
|
||||
1,0,1,1,1,1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,91,8,5,1,6,1,6,1,6,
|
||||
1,7,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,
|
||||
1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,
|
||||
14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,3,15,134,8,15,1,16,1,16,1,16,1,16,
|
||||
1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,151,8,17,1,
|
||||
18,1,18,1,18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,
|
||||
1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,1,24,1,
|
||||
24,1,24,1,24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,
|
||||
1,26,1,26,1,26,1,26,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1,27,3,27,210,
|
||||
8,27,1,28,1,28,1,29,3,29,215,8,29,1,29,4,29,218,8,29,11,29,12,29,219,1,
|
||||
29,1,29,5,29,224,8,29,10,29,12,29,227,9,29,3,29,229,8,29,1,29,1,29,3,29,
|
||||
233,8,29,1,29,4,29,236,8,29,11,29,12,29,237,3,29,240,8,29,1,29,3,29,243,
|
||||
8,29,1,29,1,29,4,29,247,8,29,11,29,12,29,248,1,29,1,29,3,29,253,8,29,1,
|
||||
29,4,29,256,8,29,11,29,12,29,257,3,29,260,8,29,3,29,262,8,29,1,30,1,30,
|
||||
1,30,1,30,5,30,268,8,30,10,30,12,30,271,9,30,1,30,1,30,1,30,1,30,1,30,5,
|
||||
30,278,8,30,10,30,12,30,281,9,30,1,30,3,30,284,8,30,1,31,1,31,5,31,288,
|
||||
8,31,10,31,12,31,291,9,31,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,34,1,34,
|
||||
1,34,1,34,1,34,1,34,1,34,4,34,307,8,34,11,34,12,34,308,5,34,311,8,34,10,
|
||||
34,12,34,314,9,34,1,35,4,35,317,8,35,11,35,12,35,318,1,35,1,35,1,36,1,36,
|
||||
1,37,4,37,326,8,37,11,37,12,37,327,0,0,38,1,1,3,2,5,3,7,4,9,5,11,6,13,7,
|
||||
15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,
|
||||
20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,28,57,0,59,29,61,30,63,
|
||||
0,65,0,67,0,69,31,71,32,73,0,75,33,1,0,29,2,0,76,76,108,108,2,0,73,73,105,
|
||||
105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98,98,2,0,84,84,116,116,
|
||||
2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120,120,2,0,83,83,115,115,
|
||||
2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112,112,2,0,67,67,99,99,2,
|
||||
0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100,2,0,72,72,104,104,2,0,
|
||||
89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102,2,0,43,43,45,45,2,0,34,
|
||||
34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97,123,7,0,35,36,45,45,47,
|
||||
58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32,32,1,0,48,57,8,0,9,10,13,
|
||||
13,32,34,39,41,44,44,60,62,91,91,93,93,353,0,1,1,0,0,0,0,3,1,0,0,0,0,5,
|
||||
1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,
|
||||
0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,
|
||||
0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,
|
||||
0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,
|
||||
0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,
|
||||
0,69,1,0,0,0,0,71,1,0,0,0,0,75,1,0,0,0,1,77,1,0,0,0,3,79,1,0,0,0,5,81,1,
|
||||
0,0,0,7,83,1,0,0,0,9,85,1,0,0,0,11,90,1,0,0,0,13,92,1,0,0,0,15,95,1,0,0,
|
||||
0,17,98,1,0,0,0,19,100,1,0,0,0,21,103,1,0,0,0,23,105,1,0,0,0,25,108,1,0,
|
||||
0,0,27,113,1,0,0,0,29,119,1,0,0,0,31,127,1,0,0,0,33,135,1,0,0,0,35,142,
|
||||
1,0,0,0,37,152,1,0,0,0,39,155,1,0,0,0,41,159,1,0,0,0,43,163,1,0,0,0,45,
|
||||
166,1,0,0,0,47,175,1,0,0,0,49,179,1,0,0,0,51,186,1,0,0,0,53,193,1,0,0,0,
|
||||
55,209,1,0,0,0,57,211,1,0,0,0,59,261,1,0,0,0,61,283,1,0,0,0,63,285,1,0,
|
||||
0,0,65,292,1,0,0,0,67,295,1,0,0,0,69,299,1,0,0,0,71,316,1,0,0,0,73,322,
|
||||
1,0,0,0,75,325,1,0,0,0,77,78,5,40,0,0,78,2,1,0,0,0,79,80,5,41,0,0,80,4,
|
||||
1,0,0,0,81,82,5,91,0,0,82,6,1,0,0,0,83,84,5,93,0,0,84,8,1,0,0,0,85,86,5,
|
||||
44,0,0,86,10,1,0,0,0,87,91,5,61,0,0,88,89,5,61,0,0,89,91,5,61,0,0,90,87,
|
||||
1,0,0,0,90,88,1,0,0,0,91,12,1,0,0,0,92,93,5,33,0,0,93,94,5,61,0,0,94,14,
|
||||
1,0,0,0,95,96,5,60,0,0,96,97,5,62,0,0,97,16,1,0,0,0,98,99,5,60,0,0,99,18,
|
||||
1,0,0,0,100,101,5,60,0,0,101,102,5,61,0,0,102,20,1,0,0,0,103,104,5,62,0,
|
||||
0,104,22,1,0,0,0,105,106,5,62,0,0,106,107,5,61,0,0,107,24,1,0,0,0,108,109,
|
||||
7,0,0,0,109,110,7,1,0,0,110,111,7,2,0,0,111,112,7,3,0,0,112,26,1,0,0,0,
|
||||
113,114,7,1,0,0,114,115,7,0,0,0,115,116,7,1,0,0,116,117,7,2,0,0,117,118,
|
||||
7,3,0,0,118,28,1,0,0,0,119,120,7,4,0,0,120,121,7,3,0,0,121,122,7,5,0,0,
|
||||
122,123,7,6,0,0,123,124,7,3,0,0,124,125,7,3,0,0,125,126,7,7,0,0,126,30,
|
||||
1,0,0,0,127,128,7,3,0,0,128,129,7,8,0,0,129,130,7,1,0,0,130,131,7,9,0,0,
|
||||
131,133,7,5,0,0,132,134,7,9,0,0,133,132,1,0,0,0,133,134,1,0,0,0,134,32,
|
||||
1,0,0,0,135,136,7,10,0,0,136,137,7,3,0,0,137,138,7,11,0,0,138,139,7,3,0,
|
||||
0,139,140,7,8,0,0,140,141,7,12,0,0,141,34,1,0,0,0,142,143,7,13,0,0,143,
|
||||
144,7,14,0,0,144,145,7,7,0,0,145,146,7,5,0,0,146,147,7,15,0,0,147,148,7,
|
||||
1,0,0,148,150,7,7,0,0,149,151,7,9,0,0,150,149,1,0,0,0,150,151,1,0,0,0,151,
|
||||
36,1,0,0,0,152,153,7,1,0,0,153,154,7,7,0,0,154,38,1,0,0,0,155,156,7,7,0,
|
||||
0,156,157,7,14,0,0,157,158,7,5,0,0,158,40,1,0,0,0,159,160,7,15,0,0,160,
|
||||
161,7,7,0,0,161,162,7,16,0,0,162,42,1,0,0,0,163,164,7,14,0,0,164,165,7,
|
||||
10,0,0,165,44,1,0,0,0,166,167,7,17,0,0,167,168,7,15,0,0,168,169,7,9,0,0,
|
||||
169,170,7,5,0,0,170,171,7,14,0,0,171,172,7,2,0,0,172,173,7,3,0,0,173,174,
|
||||
7,7,0,0,174,46,1,0,0,0,175,176,7,17,0,0,176,177,7,15,0,0,177,178,7,9,0,
|
||||
0,178,48,1,0,0,0,179,180,7,17,0,0,180,181,7,15,0,0,181,182,7,9,0,0,182,
|
||||
183,7,15,0,0,183,184,7,7,0,0,184,185,7,18,0,0,185,50,1,0,0,0,186,187,7,
|
||||
17,0,0,187,188,7,15,0,0,188,189,7,9,0,0,189,190,7,15,0,0,190,191,7,0,0,
|
||||
0,191,192,7,0,0,0,192,52,1,0,0,0,193,194,7,9,0,0,194,195,7,3,0,0,195,196,
|
||||
7,15,0,0,196,197,7,10,0,0,197,198,7,13,0,0,198,199,7,17,0,0,199,54,1,0,
|
||||
0,0,200,201,7,5,0,0,201,202,7,10,0,0,202,203,7,19,0,0,203,210,7,3,0,0,204,
|
||||
205,7,20,0,0,205,206,7,15,0,0,206,207,7,0,0,0,207,208,7,9,0,0,208,210,7,
|
||||
3,0,0,209,200,1,0,0,0,209,204,1,0,0,0,210,56,1,0,0,0,211,212,7,21,0,0,212,
|
||||
58,1,0,0,0,213,215,3,57,28,0,214,213,1,0,0,0,214,215,1,0,0,0,215,217,1,
|
||||
0,0,0,216,218,3,73,36,0,217,216,1,0,0,0,218,219,1,0,0,0,219,217,1,0,0,0,
|
||||
219,220,1,0,0,0,220,228,1,0,0,0,221,225,5,46,0,0,222,224,3,73,36,0,223,
|
||||
222,1,0,0,0,224,227,1,0,0,0,225,223,1,0,0,0,225,226,1,0,0,0,226,229,1,0,
|
||||
0,0,227,225,1,0,0,0,228,221,1,0,0,0,228,229,1,0,0,0,229,239,1,0,0,0,230,
|
||||
232,7,3,0,0,231,233,3,57,28,0,232,231,1,0,0,0,232,233,1,0,0,0,233,235,1,
|
||||
0,0,0,234,236,3,73,36,0,235,234,1,0,0,0,236,237,1,0,0,0,237,235,1,0,0,0,
|
||||
237,238,1,0,0,0,238,240,1,0,0,0,239,230,1,0,0,0,239,240,1,0,0,0,240,262,
|
||||
1,0,0,0,241,243,3,57,28,0,242,241,1,0,0,0,242,243,1,0,0,0,243,244,1,0,0,
|
||||
0,244,246,5,46,0,0,245,247,3,73,36,0,246,245,1,0,0,0,247,248,1,0,0,0,248,
|
||||
246,1,0,0,0,248,249,1,0,0,0,249,259,1,0,0,0,250,252,7,3,0,0,251,253,3,57,
|
||||
28,0,252,251,1,0,0,0,252,253,1,0,0,0,253,255,1,0,0,0,254,256,3,73,36,0,
|
||||
255,254,1,0,0,0,256,257,1,0,0,0,257,255,1,0,0,0,257,258,1,0,0,0,258,260,
|
||||
1,0,0,0,259,250,1,0,0,0,259,260,1,0,0,0,260,262,1,0,0,0,261,214,1,0,0,0,
|
||||
261,242,1,0,0,0,262,60,1,0,0,0,263,269,5,34,0,0,264,268,8,22,0,0,265,266,
|
||||
5,92,0,0,266,268,9,0,0,0,267,264,1,0,0,0,267,265,1,0,0,0,268,271,1,0,0,
|
||||
0,269,267,1,0,0,0,269,270,1,0,0,0,270,272,1,0,0,0,271,269,1,0,0,0,272,284,
|
||||
5,34,0,0,273,279,5,39,0,0,274,278,8,23,0,0,275,276,5,92,0,0,276,278,9,0,
|
||||
0,0,277,274,1,0,0,0,277,275,1,0,0,0,278,281,1,0,0,0,279,277,1,0,0,0,279,
|
||||
280,1,0,0,0,280,282,1,0,0,0,281,279,1,0,0,0,282,284,5,39,0,0,283,263,1,
|
||||
0,0,0,283,273,1,0,0,0,284,62,1,0,0,0,285,289,7,24,0,0,286,288,7,25,0,0,
|
||||
287,286,1,0,0,0,288,291,1,0,0,0,289,287,1,0,0,0,289,290,1,0,0,0,290,64,
|
||||
1,0,0,0,291,289,1,0,0,0,292,293,5,91,0,0,293,294,5,93,0,0,294,66,1,0,0,
|
||||
0,295,296,5,91,0,0,296,297,5,42,0,0,297,298,5,93,0,0,298,68,1,0,0,0,299,
|
||||
312,3,63,31,0,300,301,5,46,0,0,301,311,3,63,31,0,302,311,3,65,32,0,303,
|
||||
311,3,67,33,0,304,306,5,46,0,0,305,307,3,73,36,0,306,305,1,0,0,0,307,308,
|
||||
1,0,0,0,308,306,1,0,0,0,308,309,1,0,0,0,309,311,1,0,0,0,310,300,1,0,0,0,
|
||||
310,302,1,0,0,0,310,303,1,0,0,0,310,304,1,0,0,0,311,314,1,0,0,0,312,310,
|
||||
1,0,0,0,312,313,1,0,0,0,313,70,1,0,0,0,314,312,1,0,0,0,315,317,7,26,0,0,
|
||||
316,315,1,0,0,0,317,318,1,0,0,0,318,316,1,0,0,0,318,319,1,0,0,0,319,320,
|
||||
1,0,0,0,320,321,6,35,0,0,321,72,1,0,0,0,322,323,7,27,0,0,323,74,1,0,0,0,
|
||||
324,326,8,28,0,0,325,324,1,0,0,0,326,327,1,0,0,0,327,325,1,0,0,0,327,328,
|
||||
1,0,0,0,328,76,1,0,0,0,29,0,90,133,150,209,214,219,225,228,232,237,239,
|
||||
242,248,252,257,259,261,267,269,277,279,283,289,308,310,312,318,327,1,6,
|
||||
0,0];
|
||||
|
||||
private static __ATN: ATN;
|
||||
public static get _ATN(): ATN {
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.2
|
||||
|
||||
import {ParseTreeListener} from "antlr4";
|
||||
|
||||
|
||||
import { QueryContext } from "./FilterQueryParser";
|
||||
import { ExpressionContext } from "./FilterQueryParser";
|
||||
import { OrExpressionContext } from "./FilterQueryParser";
|
||||
import { AndExpressionContext } from "./FilterQueryParser";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser";
|
||||
import { PrimaryContext } from "./FilterQueryParser";
|
||||
import { ComparisonContext } from "./FilterQueryParser";
|
||||
import { InClauseContext } from "./FilterQueryParser";
|
||||
import { NotInClauseContext } from "./FilterQueryParser";
|
||||
import { ValueListContext } from "./FilterQueryParser";
|
||||
import { FullTextContext } from "./FilterQueryParser";
|
||||
import { FunctionCallContext } from "./FilterQueryParser";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser";
|
||||
import { FunctionParamContext } from "./FilterQueryParser";
|
||||
import { ArrayContext } from "./FilterQueryParser";
|
||||
import { ValueContext } from "./FilterQueryParser";
|
||||
import { KeyContext } from "./FilterQueryParser";
|
||||
import { QueryContext } from "./FilterQueryParser.js";
|
||||
import { ExpressionContext } from "./FilterQueryParser.js";
|
||||
import { OrExpressionContext } from "./FilterQueryParser.js";
|
||||
import { AndExpressionContext } from "./FilterQueryParser.js";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser.js";
|
||||
import { PrimaryContext } from "./FilterQueryParser.js";
|
||||
import { ComparisonContext } from "./FilterQueryParser.js";
|
||||
import { InClauseContext } from "./FilterQueryParser.js";
|
||||
import { NotInClauseContext } from "./FilterQueryParser.js";
|
||||
import { ValueListContext } from "./FilterQueryParser.js";
|
||||
import { FullTextContext } from "./FilterQueryParser.js";
|
||||
import { FunctionCallContext } from "./FilterQueryParser.js";
|
||||
import { SearchCallContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamContext } from "./FilterQueryParser.js";
|
||||
import { ArrayContext } from "./FilterQueryParser.js";
|
||||
import { ValueContext } from "./FilterQueryParser.js";
|
||||
import { KeyContext } from "./FilterQueryParser.js";
|
||||
|
||||
|
||||
/**
|
||||
@@ -147,6 +148,16 @@ export default class FilterQueryListener extends ParseTreeListener {
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitFunctionCall?: (ctx: FunctionCallContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `FilterQueryParser.searchCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterSearchCall?: (ctx: SearchCallContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `FilterQueryParser.searchCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitSearchCall?: (ctx: SearchCallContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `FilterQueryParser.functionParamList`.
|
||||
* @param ctx the parse tree
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,26 @@
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.2
|
||||
|
||||
import {ParseTreeVisitor} from 'antlr4';
|
||||
|
||||
|
||||
import { QueryContext } from "./FilterQueryParser";
|
||||
import { ExpressionContext } from "./FilterQueryParser";
|
||||
import { OrExpressionContext } from "./FilterQueryParser";
|
||||
import { AndExpressionContext } from "./FilterQueryParser";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser";
|
||||
import { PrimaryContext } from "./FilterQueryParser";
|
||||
import { ComparisonContext } from "./FilterQueryParser";
|
||||
import { InClauseContext } from "./FilterQueryParser";
|
||||
import { NotInClauseContext } from "./FilterQueryParser";
|
||||
import { ValueListContext } from "./FilterQueryParser";
|
||||
import { FullTextContext } from "./FilterQueryParser";
|
||||
import { FunctionCallContext } from "./FilterQueryParser";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser";
|
||||
import { FunctionParamContext } from "./FilterQueryParser";
|
||||
import { ArrayContext } from "./FilterQueryParser";
|
||||
import { ValueContext } from "./FilterQueryParser";
|
||||
import { KeyContext } from "./FilterQueryParser";
|
||||
import { QueryContext } from "./FilterQueryParser.js";
|
||||
import { ExpressionContext } from "./FilterQueryParser.js";
|
||||
import { OrExpressionContext } from "./FilterQueryParser.js";
|
||||
import { AndExpressionContext } from "./FilterQueryParser.js";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser.js";
|
||||
import { PrimaryContext } from "./FilterQueryParser.js";
|
||||
import { ComparisonContext } from "./FilterQueryParser.js";
|
||||
import { InClauseContext } from "./FilterQueryParser.js";
|
||||
import { NotInClauseContext } from "./FilterQueryParser.js";
|
||||
import { ValueListContext } from "./FilterQueryParser.js";
|
||||
import { FullTextContext } from "./FilterQueryParser.js";
|
||||
import { FunctionCallContext } from "./FilterQueryParser.js";
|
||||
import { SearchCallContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamContext } from "./FilterQueryParser.js";
|
||||
import { ArrayContext } from "./FilterQueryParser.js";
|
||||
import { ValueContext } from "./FilterQueryParser.js";
|
||||
import { KeyContext } from "./FilterQueryParser.js";
|
||||
|
||||
|
||||
/**
|
||||
@@ -102,6 +103,12 @@ export default class FilterQueryVisitor<Result> extends ParseTreeVisitor<Result>
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitFunctionCall?: (ctx: FunctionCallContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `FilterQueryParser.searchCall`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitSearchCall?: (ctx: SearchCallContext) => Result;
|
||||
/**
|
||||
* Visit a parse tree produced by `FilterQueryParser.functionParamList`.
|
||||
* @param ctx the parse tree
|
||||
|
||||
@@ -380,6 +380,19 @@ describe('extractQueryPairs', () => {
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('does not turn a search() term into a pair', () => {
|
||||
// The bare form lexes as a KEY; left in it becomes a phantom filter item.
|
||||
expect(extractQueryPairs("search('err')")).toStrictEqual([]);
|
||||
expect(extractQueryPairs('search(err)')).toStrictEqual([]);
|
||||
expect(extractQueryPairs('search(')).toStrictEqual([]);
|
||||
|
||||
expect(
|
||||
extractQueryPairs("search(err) AND service.name = 'api'").map(
|
||||
(pair) => pair.key,
|
||||
),
|
||||
).toStrictEqual(['service.name']);
|
||||
});
|
||||
|
||||
it('should treat lowercase exists as non-value operator', () => {
|
||||
const input = 'body exists service.name contains "test"';
|
||||
const result = extractQueryPairs(input);
|
||||
@@ -821,3 +834,17 @@ describe('getQueryContextAtCursor - partial operator', () => {
|
||||
expect(ctx.operatorToken).toBe('k');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getQueryContextAtCursor - function context', () => {
|
||||
// Each function keyword gets its own lexer token, and every one has to be
|
||||
// registered as a function token for the editor to offer the function list.
|
||||
it.each(['has', 'hasAny', 'hasAll', 'hasToken', 'search'])(
|
||||
'resolves %s to function context',
|
||||
(functionName) => {
|
||||
const ctx = getQueryContextAtCursor(functionName, functionName.length);
|
||||
|
||||
expect(ctx.isInFunction).toBe(true);
|
||||
expect(ctx.isInKey).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1279,6 +1279,41 @@ export function getQueryContextAtCursor(
|
||||
}
|
||||
}
|
||||
|
||||
// The grammar skips whitespace outright, so hidden-channel tokens do not reach the
|
||||
// stream today -- but the rest of this file guards against them, so keep the
|
||||
// assumption in one place rather than spread across callers.
|
||||
function nextVisibleIndex(tokens: IToken[], start: number): number {
|
||||
let index = start;
|
||||
while (index < tokens.length && tokens[index].channel !== 0) {
|
||||
index += 1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// Returns the token index just past the parenthesised argument list at
|
||||
// argumentStart, or argumentStart when none follows (a call the user is still
|
||||
// typing). An unclosed list consumes the remainder.
|
||||
function indexPastArguments(tokens: IToken[], argumentStart: number): number {
|
||||
let index = nextVisibleIndex(tokens, argumentStart);
|
||||
if (index >= tokens.length || tokens[index].type !== FilterQueryLexer.LPAREN) {
|
||||
return argumentStart;
|
||||
}
|
||||
|
||||
let depth = 0;
|
||||
for (; index < tokens.length; index++) {
|
||||
if (tokens[index].type === FilterQueryLexer.LPAREN) {
|
||||
depth += 1;
|
||||
} else if (tokens[index].type === FilterQueryLexer.RPAREN) {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
return index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all key-operator-value triplets from a query string
|
||||
* This is useful for getting value suggestions based on the current key and operator
|
||||
@@ -1324,6 +1359,14 @@ export function extractQueryPairs(query: string): IQueryPair[] {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A search() term is free text, not a key: the bare form search(x) lexes
|
||||
// as one, and left in it surfaces as a phantom filter item -- the log
|
||||
// detail drawer rebuilds its filters from these pairs.
|
||||
if (token.type === FilterQueryLexer.SEARCH) {
|
||||
iterator = indexPastArguments(allTokens, iterator);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If token is a KEY, start a new pair
|
||||
if (
|
||||
token.type === FilterQueryLexer.KEY &&
|
||||
|
||||
@@ -77,6 +77,7 @@ export function isFunctionToken(tokenType: number): boolean {
|
||||
FilterQueryLexer.HASANY,
|
||||
FilterQueryLexer.HASALL,
|
||||
FilterQueryLexer.HASTOKEN,
|
||||
FilterQueryLexer.SEARCH,
|
||||
].includes(tokenType);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ var daemonSetsTableMetricNamesList = []string{
|
||||
"k8s.daemonset.misscheduled_nodes",
|
||||
}
|
||||
|
||||
// Carried forward from v1 daemonSetAttrsToEnrich
|
||||
// (pkg/query-service/app/inframetrics/daemonsets.go:29-33).
|
||||
// Carried forward from v1 daemonSetAttrsToEnrich (removed).
|
||||
var daemonSetAttrKeysForMetadata = []string{
|
||||
"k8s.daemonset.name",
|
||||
"k8s.namespace.name",
|
||||
|
||||
@@ -35,8 +35,7 @@ var deploymentsTableMetricNamesList = []string{
|
||||
"k8s.deployment.available",
|
||||
}
|
||||
|
||||
// Carried forward from v1 deploymentAttrsToEnrich
|
||||
// (pkg/query-service/app/inframetrics/deployments.go:29-33).
|
||||
// Carried forward from v1 deploymentAttrsToEnrich (removed).
|
||||
var deploymentAttrKeysForMetadata = []string{
|
||||
"k8s.deployment.name",
|
||||
"k8s.namespace.name",
|
||||
|
||||
@@ -37,8 +37,7 @@ var jobsTableMetricNamesList = []string{
|
||||
"k8s.job.desired_successful_pods",
|
||||
}
|
||||
|
||||
// Carried forward from v1 jobAttrsToEnrich
|
||||
// (pkg/query-service/app/inframetrics/jobs.go:31-35).
|
||||
// Carried forward from v1 jobAttrsToEnrich (removed).
|
||||
var jobAttrKeysForMetadata = []string{
|
||||
"k8s.job.name",
|
||||
"k8s.namespace.name",
|
||||
|
||||
@@ -35,8 +35,7 @@ var statefulSetsTableMetricNamesList = []string{
|
||||
"k8s.statefulset.current_pods",
|
||||
}
|
||||
|
||||
// Carried forward from v1 statefulSetAttrsToEnrich
|
||||
// (pkg/query-service/app/inframetrics/statefulsets.go:29-33).
|
||||
// Carried forward from v1 statefulSetAttrsToEnrich (removed).
|
||||
var statefulSetAttrKeysForMetadata = []string{
|
||||
"k8s.statefulset.name",
|
||||
"k8s.namespace.name",
|
||||
|
||||
@@ -26,8 +26,7 @@ var volumesTableMetricNamesList = []string{
|
||||
"k8s.volume.inodes.used",
|
||||
}
|
||||
|
||||
// Carried forward from v1 volumeAttrsToEnrich
|
||||
// (pkg/query-service/app/inframetrics/pvcs.go:23-31).
|
||||
// Carried forward from v1 volumeAttrsToEnrich (removed).
|
||||
var volumeAttrKeysForMetadata = []string{
|
||||
"k8s.persistentvolumeclaim.name",
|
||||
"k8s.pod.uid",
|
||||
|
||||
@@ -3334,60 +3334,6 @@ func (r *ClickHouseReader) GetMetricMetadata(ctx context.Context, orgID valuer.U
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCountOfThings returns the count of things in the query
|
||||
// This is a generic function that can be used to check if any data exists for a given query
|
||||
func (r *ClickHouseReader) GetCountOfThings(ctx context.Context, query string) (uint64, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetCountOfThings",
|
||||
})
|
||||
var count uint64
|
||||
err := r.db.QueryRow(ctx, query).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetActiveHostsFromMetricMetadata(ctx context.Context, metricNames []string, hostNameAttr string, sinceUnixMilli int64) (map[string]bool, error) {
|
||||
activeHosts := map[string]bool{}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
`SELECT DISTINCT attr_string_value
|
||||
FROM %s.%s
|
||||
WHERE metric_name IN @metricNames
|
||||
AND attr_name = @attrName
|
||||
AND last_reported_unix_milli >= @sinceUnixMilli`,
|
||||
signozMetricDBName,
|
||||
constants.SIGNOZ_METADATA_TABLENAME,
|
||||
)
|
||||
|
||||
rows, err := r.db.Query(ctx, query,
|
||||
clickhouse.Named("metricNames", metricNames),
|
||||
clickhouse.Named("attrName", hostNameAttr),
|
||||
clickhouse.Named("sinceUnixMilli", sinceUnixMilli),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errorsV2.WrapInternalf(err, errorsV2.CodeInternal, "error querying active hosts")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var hostName string
|
||||
if err := rows.Scan(&hostName); err != nil {
|
||||
return nil, errorsV2.WrapInternalf(err, errorsV2.CodeInternal, "error scanning active host row")
|
||||
}
|
||||
if hostName != "" {
|
||||
activeHosts[hostName] = true
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, errorsV2.WrapInternalf(err, errorsV2.CodeInternal, "error iterating active host rows")
|
||||
}
|
||||
|
||||
return activeHosts, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetLatestReceivedMetric(
|
||||
ctx context.Context, orgID valuer.UUID, metricNames []string, labelValues map[string]string,
|
||||
) (*model.MetricStatus, *model.ApiError) {
|
||||
@@ -4135,6 +4081,10 @@ func (r *ClickHouseReader) GetTimeSeriesResultV3(ctx context.Context, query stri
|
||||
return readRowsForTimeSeriesResult(rows, vars, columnNames, countOfNumberCols)
|
||||
}
|
||||
|
||||
func isJSONColumn(columnType driver.ColumnType) bool {
|
||||
return strings.HasPrefix(strings.ToUpper(columnType.DatabaseTypeName()), "JSON")
|
||||
}
|
||||
|
||||
// GetListResultV3 runs the query and returns list of rows
|
||||
func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([]*v3.Row, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
@@ -4159,6 +4109,12 @@ func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([
|
||||
for rows.Next() {
|
||||
var vars = make([]interface{}, len(columnTypes))
|
||||
for i := range columnTypes {
|
||||
if isJSONColumn(columnTypes[i]) {
|
||||
// the driver fails to decode JSON into native Go values, so it is read as raw bytes
|
||||
var raw []byte
|
||||
vars[i] = &raw
|
||||
continue
|
||||
}
|
||||
vars[i] = reflect.New(columnTypes[i].ScanType()).Interface()
|
||||
}
|
||||
if err := rows.Scan(vars...); err != nil {
|
||||
@@ -4167,7 +4123,17 @@ func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([
|
||||
row := map[string]interface{}{}
|
||||
var t time.Time
|
||||
for idx, v := range vars {
|
||||
if columnNames[idx] == "timestamp" {
|
||||
if isJSONColumn(columnTypes[idx]) {
|
||||
raw, ok := v.(*[]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var value map[string]interface{}
|
||||
if err := json.Unmarshal(*raw, &value); err != nil {
|
||||
return nil, errors.New(err.Error())
|
||||
}
|
||||
row[columnNames[idx]] = value
|
||||
} else if columnNames[idx] == "timestamp" {
|
||||
switch v := v.(type) {
|
||||
case *uint64:
|
||||
t = time.Unix(0, int64(*v))
|
||||
@@ -4200,33 +4166,6 @@ func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([
|
||||
|
||||
}
|
||||
|
||||
// GetHostMetricsExistenceAndEarliestTime returns (count, minFirstReportedUnixMilli, error) for the given host metric names
|
||||
// from distributed_metadata. When count is 0, minFirstReportedUnixMilli is 0.
|
||||
func (r *ClickHouseReader) GetMetricsExistenceAndEarliestTime(ctx context.Context, metricNames []string) (uint64, uint64, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalMetrics.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "clickhouse-reader",
|
||||
instrumentationtypes.CodeFunctionName: "GetMetricsExistenceAndEarliestTime",
|
||||
})
|
||||
if len(metricNames) == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
`SELECT count(*) AS cnt, min(first_reported_unix_milli) AS min_first_reported
|
||||
FROM %s.%s
|
||||
WHERE metric_name IN @metric_names`,
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_METADATA_TABLENAME)
|
||||
|
||||
var count, minFirstReported uint64
|
||||
err := r.db.QueryRow(ctx, query, clickhouse.Named("metric_names", metricNames)).Scan(&count, &minFirstReported)
|
||||
if err != nil {
|
||||
r.logger.Error("error getting host metrics existence and earliest time", errorsV2.Attr(err))
|
||||
return 0, 0, err
|
||||
}
|
||||
return count, minFirstReported, nil
|
||||
}
|
||||
|
||||
func getPersonalisedError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
|
||||
@@ -44,7 +44,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/contextlinks"
|
||||
traceFunnelsModule "github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/inframetrics"
|
||||
queues2 "github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/logs"
|
||||
logsv3 "github.com/SigNoz/signoz/pkg/query-service/app/logs/v3"
|
||||
@@ -120,20 +119,6 @@ type APIHandler struct {
|
||||
// Websocket connection upgrader
|
||||
Upgrader *websocket.Upgrader
|
||||
|
||||
hostsRepo *inframetrics.HostsRepo
|
||||
processesRepo *inframetrics.ProcessesRepo
|
||||
podsRepo *inframetrics.PodsRepo
|
||||
nodesRepo *inframetrics.NodesRepo
|
||||
namespacesRepo *inframetrics.NamespacesRepo
|
||||
clustersRepo *inframetrics.ClustersRepo
|
||||
// workloads
|
||||
deploymentsRepo *inframetrics.DeploymentsRepo
|
||||
daemonsetsRepo *inframetrics.DaemonSetsRepo
|
||||
statefulsetsRepo *inframetrics.StatefulSetsRepo
|
||||
jobsRepo *inframetrics.JobsRepo
|
||||
|
||||
pvcsRepo *inframetrics.PvcsRepo
|
||||
|
||||
LicensingAPI licensing.API
|
||||
|
||||
QueryParserAPI *queryparser.API
|
||||
@@ -180,17 +165,6 @@ func NewAPIHandler(opts APIHandlerOpts, config signoz.Config) (*APIHandler, erro
|
||||
querier := querier.NewQuerier(querierOpts)
|
||||
querierv2 := querierV2.NewQuerier(querierOptsV2)
|
||||
|
||||
hostsRepo := inframetrics.NewHostsRepo(opts.Reader, querierv2)
|
||||
processesRepo := inframetrics.NewProcessesRepo(opts.Reader, querierv2)
|
||||
podsRepo := inframetrics.NewPodsRepo(opts.Reader, querierv2)
|
||||
nodesRepo := inframetrics.NewNodesRepo(opts.Reader, querierv2)
|
||||
namespacesRepo := inframetrics.NewNamespacesRepo(opts.Reader, querierv2)
|
||||
clustersRepo := inframetrics.NewClustersRepo(opts.Reader, querierv2)
|
||||
deploymentsRepo := inframetrics.NewDeploymentsRepo(opts.Reader, querierv2)
|
||||
daemonsetsRepo := inframetrics.NewDaemonSetsRepo(opts.Reader, querierv2)
|
||||
statefulsetsRepo := inframetrics.NewStatefulSetsRepo(opts.Reader, querierv2)
|
||||
jobsRepo := inframetrics.NewJobsRepo(opts.Reader, querierv2)
|
||||
pvcsRepo := inframetrics.NewPvcsRepo(opts.Reader, querierv2)
|
||||
//quickFilterModule := quickfilter.NewAPI(opts.QuickFilterModule)
|
||||
|
||||
aH := &APIHandler{
|
||||
@@ -202,17 +176,6 @@ func NewAPIHandler(opts APIHandlerOpts, config signoz.Config) (*APIHandler, erro
|
||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||
querier: querier,
|
||||
querierV2: querierv2,
|
||||
hostsRepo: hostsRepo,
|
||||
processesRepo: processesRepo,
|
||||
podsRepo: podsRepo,
|
||||
nodesRepo: nodesRepo,
|
||||
namespacesRepo: namespacesRepo,
|
||||
clustersRepo: clustersRepo,
|
||||
deploymentsRepo: deploymentsRepo,
|
||||
daemonsetsRepo: daemonsetsRepo,
|
||||
statefulsetsRepo: statefulsetsRepo,
|
||||
jobsRepo: jobsRepo,
|
||||
pvcsRepo: pvcsRepo,
|
||||
LicensingAPI: opts.LicensingAPI,
|
||||
Signoz: opts.Signoz,
|
||||
QueryParserAPI: opts.QueryParserAPI,
|
||||
@@ -404,66 +367,6 @@ func (aH *APIHandler) RegisterQueryRangeV3Routes(router *mux.Router, am *middlew
|
||||
subRouter.HandleFunc("/logs/livetail", am.ViewAccess(aH.Signoz.Handlers.QuerierHandler.QueryRawStream)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) RegisterInfraMetricsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
hostsSubRouter := router.PathPrefix("/api/v1/hosts").Subrouter()
|
||||
hostsSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getHostAttributeKeys)).Methods(http.MethodGet)
|
||||
hostsSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getHostAttributeValues)).Methods(http.MethodGet)
|
||||
hostsSubRouter.HandleFunc("/list", am.ViewAccess(aH.getHostList)).Methods(http.MethodPost)
|
||||
|
||||
processesSubRouter := router.PathPrefix("/api/v1/processes").Subrouter()
|
||||
processesSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getProcessAttributeKeys)).Methods(http.MethodGet)
|
||||
processesSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getProcessAttributeValues)).Methods(http.MethodGet)
|
||||
processesSubRouter.HandleFunc("/list", am.ViewAccess(aH.getProcessList)).Methods(http.MethodPost)
|
||||
|
||||
podsSubRouter := router.PathPrefix("/api/v1/pods").Subrouter()
|
||||
podsSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getPodAttributeKeys)).Methods(http.MethodGet)
|
||||
podsSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getPodAttributeValues)).Methods(http.MethodGet)
|
||||
podsSubRouter.HandleFunc("/list", am.ViewAccess(aH.getPodList)).Methods(http.MethodPost)
|
||||
|
||||
pvcsSubRouter := router.PathPrefix("/api/v1/pvcs").Subrouter()
|
||||
pvcsSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getPvcAttributeKeys)).Methods(http.MethodGet)
|
||||
pvcsSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getPvcAttributeValues)).Methods(http.MethodGet)
|
||||
pvcsSubRouter.HandleFunc("/list", am.ViewAccess(aH.getPvcList)).Methods(http.MethodPost)
|
||||
|
||||
nodesSubRouter := router.PathPrefix("/api/v1/nodes").Subrouter()
|
||||
nodesSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getNodeAttributeKeys)).Methods(http.MethodGet)
|
||||
nodesSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getNodeAttributeValues)).Methods(http.MethodGet)
|
||||
nodesSubRouter.HandleFunc("/list", am.ViewAccess(aH.getNodeList)).Methods(http.MethodPost)
|
||||
|
||||
namespacesSubRouter := router.PathPrefix("/api/v1/namespaces").Subrouter()
|
||||
namespacesSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getNamespaceAttributeKeys)).Methods(http.MethodGet)
|
||||
namespacesSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getNamespaceAttributeValues)).Methods(http.MethodGet)
|
||||
namespacesSubRouter.HandleFunc("/list", am.ViewAccess(aH.getNamespaceList)).Methods(http.MethodPost)
|
||||
|
||||
clustersSubRouter := router.PathPrefix("/api/v1/clusters").Subrouter()
|
||||
clustersSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getClusterAttributeKeys)).Methods(http.MethodGet)
|
||||
clustersSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getClusterAttributeValues)).Methods(http.MethodGet)
|
||||
clustersSubRouter.HandleFunc("/list", am.ViewAccess(aH.getClusterList)).Methods(http.MethodPost)
|
||||
|
||||
deploymentsSubRouter := router.PathPrefix("/api/v1/deployments").Subrouter()
|
||||
deploymentsSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getDeploymentAttributeKeys)).Methods(http.MethodGet)
|
||||
deploymentsSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getDeploymentAttributeValues)).Methods(http.MethodGet)
|
||||
deploymentsSubRouter.HandleFunc("/list", am.ViewAccess(aH.getDeploymentList)).Methods(http.MethodPost)
|
||||
|
||||
daemonsetsSubRouter := router.PathPrefix("/api/v1/daemonsets").Subrouter()
|
||||
daemonsetsSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getDaemonSetAttributeKeys)).Methods(http.MethodGet)
|
||||
daemonsetsSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getDaemonSetAttributeValues)).Methods(http.MethodGet)
|
||||
daemonsetsSubRouter.HandleFunc("/list", am.ViewAccess(aH.getDaemonSetList)).Methods(http.MethodPost)
|
||||
|
||||
statefulsetsSubRouter := router.PathPrefix("/api/v1/statefulsets").Subrouter()
|
||||
statefulsetsSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getStatefulSetAttributeKeys)).Methods(http.MethodGet)
|
||||
statefulsetsSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getStatefulSetAttributeValues)).Methods(http.MethodGet)
|
||||
statefulsetsSubRouter.HandleFunc("/list", am.ViewAccess(aH.getStatefulSetList)).Methods(http.MethodPost)
|
||||
|
||||
jobsSubRouter := router.PathPrefix("/api/v1/jobs").Subrouter()
|
||||
jobsSubRouter.HandleFunc("/attribute_keys", am.ViewAccess(aH.getJobAttributeKeys)).Methods(http.MethodGet)
|
||||
jobsSubRouter.HandleFunc("/attribute_values", am.ViewAccess(aH.getJobAttributeValues)).Methods(http.MethodGet)
|
||||
jobsSubRouter.HandleFunc("/list", am.ViewAccess(aH.getJobList)).Methods(http.MethodPost)
|
||||
|
||||
infraOnboardingSubRouter := router.PathPrefix("/api/v1/infra_onboarding").Subrouter()
|
||||
infraOnboardingSubRouter.HandleFunc("/k8s/status", am.ViewAccess(aH.getK8sInfraOnboardingStatus)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) RegisterWebSocketPaths(router *mux.Router, am *middleware.AuthZ) {
|
||||
subRouter := router.PathPrefix("/ws").Subrouter()
|
||||
subRouter.HandleFunc("/query_progress", am.ViewAccess(aH.GetQueryProgressUpdates)).Methods(http.MethodGet)
|
||||
@@ -3803,6 +3706,10 @@ func (aH *APIHandler) QueryRangeV3(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
queryRangeParams.UseJSONBody = aH.Signoz.Flagger.BooleanOrEmpty(
|
||||
r.Context(), flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID),
|
||||
)
|
||||
|
||||
// add temporality for each metric
|
||||
temporalityErr := aH.PopulateTemporality(r.Context(), orgID, queryRangeParams)
|
||||
if temporalityErr != nil {
|
||||
|
||||
@@ -1,908 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func (aH *APIHandler) getHostAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// get attribute keys
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.hostsRepo.GetHostAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// write response
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getHostAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
// parse request
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// get attribute values
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.hostsRepo.GetHostAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// write response
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getHostList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.HostListRequest{}
|
||||
// parse request
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// get host list
|
||||
hostList, err := aH.hostsRepo.GetHostList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// write response
|
||||
aH.Respond(w, hostList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getProcessAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.processesRepo.GetProcessAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getProcessAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.processesRepo.GetProcessAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getProcessList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.ProcessListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
hostList, err := aH.processesRepo.GetProcessList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, hostList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getPodAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.podsRepo.GetPodAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getPodAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.podsRepo.GetPodAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getPodList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.PodListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
podList, err := aH.podsRepo.GetPodList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, podList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getNodeAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.nodesRepo.GetNodeAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getNodeAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.nodesRepo.GetNodeAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getNodeList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.NodeListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
nodeList, err := aH.nodesRepo.GetNodeList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, nodeList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getNamespaceAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
keys, err := aH.namespacesRepo.GetNamespaceAttributeKeys(ctx, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getNamespaceAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.namespacesRepo.GetNamespaceAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getNamespaceList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.NamespaceListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
namespaceList, err := aH.namespacesRepo.GetNamespaceList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, namespaceList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getClusterAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.clustersRepo.GetClusterAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getClusterAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.clustersRepo.GetClusterAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getClusterList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.ClusterListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
clusterList, err := aH.clustersRepo.GetClusterList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, clusterList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getDeploymentAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.deploymentsRepo.GetDeploymentAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getDeploymentAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.deploymentsRepo.GetDeploymentAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getDeploymentList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.DeploymentListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
deploymentList, err := aH.deploymentsRepo.GetDeploymentList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, deploymentList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getDaemonSetAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.daemonsetsRepo.GetDaemonSetAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getDaemonSetAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.daemonsetsRepo.GetDaemonSetAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getDaemonSetList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.DaemonSetListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
daemonSetList, err := aH.daemonsetsRepo.GetDaemonSetList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, daemonSetList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getStatefulSetAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.statefulsetsRepo.GetStatefulSetAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getStatefulSetAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.statefulsetsRepo.GetStatefulSetAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getStatefulSetList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.StatefulSetListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
statefulSetList, err := aH.statefulsetsRepo.GetStatefulSetList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, statefulSetList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getJobAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.jobsRepo.GetJobAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getJobAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.jobsRepo.GetJobAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getJobList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.JobListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
jobList, err := aH.jobsRepo.GetJobList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, jobList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getPvcList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := model.VolumeListRequest{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
pvcList, err := aH.pvcsRepo.GetPvcList(ctx, orgID, req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, pvcList)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getPvcAttributeKeys(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeKeyRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
keys, err := aH.pvcsRepo.GetPvcAttributeKeys(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, keys)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getPvcAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
req, err := parseFilterAttributeValueRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
values, err := aH.pvcsRepo.GetPvcAttributeValues(ctx, orgID, *req)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, values)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getK8sInfraOnboardingStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
status := model.OnboardingStatus{}
|
||||
|
||||
didSendPodMetrics, err := aH.podsRepo.DidSendPodMetrics(ctx)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if !didSendPodMetrics {
|
||||
aH.Respond(w, status)
|
||||
return
|
||||
}
|
||||
|
||||
didSendClusterMetrics, err := aH.podsRepo.DidSendClusterMetrics(ctx)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
didSendNodeMetrics, err := aH.nodesRepo.DidSendNodeMetrics(ctx)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
didSendOptionalPodMetrics, err := aH.podsRepo.IsSendingOptionalPodMetrics(ctx)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
requiredMetadata, err := aH.podsRepo.SendingRequiredMetadata(ctx)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
status.DidSendPodMetrics = didSendPodMetrics
|
||||
status.DidSendClusterMetrics = didSendClusterMetrics
|
||||
status.DidSendNodeMetrics = didSendNodeMetrics
|
||||
status.IsSendingOptionalPodMetrics = didSendOptionalPodMetrics
|
||||
status.IsSendingRequiredMetadata = requiredMetadata
|
||||
|
||||
aH.Respond(w, status)
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForClusters = "k8s.node.cpu.usage"
|
||||
|
||||
clusterAttrsToEnrich = []string{"k8s.cluster.name"}
|
||||
|
||||
// TODO(srikanthccv): change this to k8s_cluster_uid after showing the missing data banner
|
||||
k8sClusterUIDAttrKey = "k8s.cluster.name"
|
||||
|
||||
queryNamesForClusters = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"cpu_allocatable": {"B"},
|
||||
"memory": {"C"},
|
||||
"memory_allocatable": {"D"},
|
||||
}
|
||||
clusterQueryNames = []string{"A", "B", "C", "D"}
|
||||
)
|
||||
|
||||
type ClustersRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewClustersRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *ClustersRepo {
|
||||
return &ClustersRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (n *ClustersRepo) GetClusterAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForClusters
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := n.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeKeysResponse, nil
|
||||
}
|
||||
|
||||
func (n *ClustersRepo) GetClusterAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForClusters
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := n.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (p *ClustersRepo) getMetadataAttributes(ctx context.Context, req model.ClusterListRequest) (map[string]map[string]string, error) {
|
||||
clusterAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range clusterAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForClusters,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := p.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
clusterUID := stringData[k8sClusterUIDAttrKey]
|
||||
if _, ok := clusterAttrs[clusterUID]; !ok {
|
||||
clusterAttrs[clusterUID] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
clusterAttrs[clusterUID][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return clusterAttrs, nil
|
||||
}
|
||||
|
||||
func (p *ClustersRepo) getTopClusterGroups(ctx context.Context, orgID valuer.UUID, req model.ClusterListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopClusters(req)
|
||||
|
||||
queryNames := queryNamesForClusters[req.OrderBy.ColumnName]
|
||||
topClusterGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topClusterGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopClusterGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topClusterGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topClusterGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
max := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopClusterGroupsSeries := formattedResponse[0].Series[req.Offset:int(max)]
|
||||
|
||||
topClusterGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopClusterGroupsSeries {
|
||||
topClusterGroups = append(topClusterGroups, series.Labels)
|
||||
}
|
||||
allClusterGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allClusterGroups = append(allClusterGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topClusterGroups, allClusterGroups, nil
|
||||
}
|
||||
|
||||
func (p *ClustersRepo) GetClusterList(ctx context.Context, orgID valuer.UUID, req model.ClusterListRequest) (model.ClusterListResponse, error) {
|
||||
resp := model.ClusterListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sClusterUIDAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := NodesTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
}
|
||||
|
||||
clusterAttrs, err := p.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topClusterGroups, allClusterGroups, err := p.getTopClusterGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topClusterGroup := range topClusterGroups {
|
||||
for k, v := range topClusterGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetClusterList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.ClusterListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.ClusterListRecord{
|
||||
CPUUsage: -1,
|
||||
CPUAllocatable: -1,
|
||||
MemoryUsage: -1,
|
||||
MemoryAllocatable: -1,
|
||||
}
|
||||
|
||||
if clusterUID, ok := row.Data[k8sClusterUIDAttrKey].(string); ok {
|
||||
record.ClusterUID = clusterUID
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.CPUUsage = cpu
|
||||
}
|
||||
|
||||
if cpuAllocatable, ok := row.Data["B"].(float64); ok {
|
||||
record.CPUAllocatable = cpuAllocatable
|
||||
}
|
||||
|
||||
if mem, ok := row.Data["C"].(float64); ok {
|
||||
record.MemoryUsage = mem
|
||||
}
|
||||
|
||||
if memoryAllocatable, ok := row.Data["D"].(float64); ok {
|
||||
record.MemoryAllocatable = memoryAllocatable
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := clusterAttrs[record.ClusterUID]; ok && record.ClusterUID != "" {
|
||||
record.Meta = clusterAttrs[record.ClusterUID]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(clusterQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allClusterGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
const fromWhereQuery = `
|
||||
FROM %s.%s
|
||||
WHERE metric_name IN (%s)
|
||||
AND unix_milli >= toUnixTimestamp(now() - toIntervalMinute(60)) * 1000
|
||||
`
|
||||
|
||||
var (
|
||||
// TODO(srikanthccv): import metadata yaml from receivers and use generated files to check the metrics
|
||||
podMetricNamesToCheck = []string{
|
||||
"k8s.pod.cpu.usage",
|
||||
"k8s.pod.memory.working_set",
|
||||
"k8s.pod.cpu_request_utilization",
|
||||
"k8s.pod.memory_request_utilization",
|
||||
"k8s.pod.cpu_limit_utilization",
|
||||
"k8s.pod.memory_limit_utilization",
|
||||
"k8s.container.restarts",
|
||||
"k8s.pod.phase",
|
||||
}
|
||||
nodeMetricNamesToCheck = []string{
|
||||
"k8s.node.cpu.usage",
|
||||
"k8s.node.allocatable_cpu",
|
||||
"k8s.node.memory.working_set",
|
||||
"k8s.node.allocatable_memory",
|
||||
"k8s.node.condition_ready",
|
||||
}
|
||||
clusterMetricNamesToCheck = []string{
|
||||
"k8s.daemonset.desired_scheduled_nodes",
|
||||
"k8s.daemonset.current_scheduled_nodes",
|
||||
"k8s.deployment.desired",
|
||||
"k8s.deployment.available",
|
||||
"k8s.job.desired_successful_pods",
|
||||
"k8s.job.active_pods",
|
||||
"k8s.job.failed_pods",
|
||||
"k8s.job.successful_pods",
|
||||
"k8s.statefulset.desired_pods",
|
||||
"k8s.statefulset.current_pods",
|
||||
}
|
||||
optionalPodMetricNamesToCheck = []string{
|
||||
"k8s.pod.cpu_request_utilization",
|
||||
"k8s.pod.memory_request_utilization",
|
||||
"k8s.pod.cpu_limit_utilization",
|
||||
"k8s.pod.memory_limit_utilization",
|
||||
}
|
||||
|
||||
// did they ever send _any_ pod metrics?
|
||||
didSendPodMetricsQuery = `
|
||||
SELECT count() FROM %s.%s WHERE metric_name IN (%s)
|
||||
`
|
||||
|
||||
// did they ever send any node metrics?
|
||||
didSendNodeMetricsQuery = `
|
||||
SELECT count() FROM %s.%s WHERE metric_name IN (%s)
|
||||
`
|
||||
|
||||
// did they ever send any cluster metrics?
|
||||
didSendClusterMetricsQuery = `
|
||||
SELECT count() FROM %s.%s WHERE metric_name IN (%s)
|
||||
`
|
||||
|
||||
// if they ever sent _any_ pod metrics, we assume they know how to send pod metrics
|
||||
// now, are they sending optional pod metrics such request/limit metrics?
|
||||
isSendingOptionalPodMetricsQuery = `
|
||||
SELECT count() FROM %s.%s WHERE metric_name IN (%s)
|
||||
`
|
||||
|
||||
// there should be [cluster, node, namespace, one of (deployment, statefulset, daemonset, cronjob, job)] for each pod
|
||||
|
||||
selectQuery = fmt.Sprintf(`
|
||||
SELECT
|
||||
any(JSONExtractString(labels, '%s')) as k8s_cluster_name,
|
||||
any(JSONExtractString(labels, '%s')) as k8s_node_name,
|
||||
any(JSONExtractString(labels, '%s')) as k8s_namespace_name,
|
||||
any(JSONExtractString(labels, '%s')) as k8s_deployment_name,
|
||||
any(JSONExtractString(labels, '%s')) as k8s_statefulset_name,
|
||||
any(JSONExtractString(labels, '%s')) as k8s_daemonset_name,
|
||||
any(JSONExtractString(labels, '%s')) as k8s_cronjob_name,
|
||||
any(JSONExtractString(labels, '%s')) as k8s_job_name,
|
||||
JSONExtractString(labels, '%s') as k8s_pod_name
|
||||
`,
|
||||
"k8s.cluster.name",
|
||||
"k8s.node.name",
|
||||
"k8s.namespace.name",
|
||||
"k8s.deployment.name",
|
||||
"k8s.statefulset.name",
|
||||
"k8s.daemonset.name",
|
||||
"k8s.cronjob.name",
|
||||
"k8s.job.name",
|
||||
"k8s.pod.name",
|
||||
)
|
||||
|
||||
filterGroupQuery = fmt.Sprintf(`
|
||||
AND JSONExtractString(labels, '%s')
|
||||
NOT IN ('kube-system','kube-public','kube-node-lease','metallb-system')
|
||||
GROUP BY k8s_pod_name
|
||||
LIMIT 1 BY k8s_cluster_name, k8s_node_name, k8s_namespace_name
|
||||
`,
|
||||
"k8s.namespace.name",
|
||||
)
|
||||
|
||||
isSendingRequiredMetadataQuery = selectQuery + fromWhereQuery + filterGroupQuery
|
||||
)
|
||||
|
||||
// getParamsForTopItems returns the step, time series table name and samples table name
|
||||
// for the top items query. what are we doing here?
|
||||
// we want to identify the top hosts/pods/nodes quickly, so we use pre-aggregated data
|
||||
// for samples and time series tables to speed up the query
|
||||
// the speed of the query depends on the number of values in group by clause, the higher
|
||||
// the step interval, the faster the query will be as number of rows to group by is reduced
|
||||
// here we are using the averaged value of the time series data to get the top items
|
||||
func getParamsForTopItems(start, end int64) (int64, string, string) {
|
||||
var step int64
|
||||
var timeSeriesTableName string
|
||||
var samplesTableName string
|
||||
|
||||
if end-start < time.Hour.Milliseconds() {
|
||||
// 5 minute aggregation for any query less than 1 hour
|
||||
step = 5 * 60
|
||||
timeSeriesTableName = constants.SIGNOZ_TIMESERIES_v4_LOCAL_TABLENAME
|
||||
samplesTableName = constants.SIGNOZ_SAMPLES_V4_AGG_5M_TABLENAME
|
||||
} else if end-start < time.Hour.Milliseconds()*6 {
|
||||
// 15 minute aggregation for any query less than 6 hours
|
||||
step = 15 * 60
|
||||
timeSeriesTableName = constants.SIGNOZ_TIMESERIES_v4_6HRS_LOCAL_TABLENAME
|
||||
samplesTableName = constants.SIGNOZ_SAMPLES_V4_AGG_5M_TABLENAME
|
||||
} else if end-start < time.Hour.Milliseconds()*24 {
|
||||
// 1 hour aggregation for any query less than 1 day
|
||||
step = 60 * 60
|
||||
timeSeriesTableName = constants.SIGNOZ_TIMESERIES_v4_1DAY_LOCAL_TABLENAME
|
||||
samplesTableName = constants.SIGNOZ_SAMPLES_V4_AGG_30M_TABLENAME
|
||||
} else if end-start < time.Hour.Milliseconds()*7 {
|
||||
// 6 hours aggregation for any query less than 1 week
|
||||
step = 6 * 60 * 60
|
||||
timeSeriesTableName = constants.SIGNOZ_TIMESERIES_v4_1WEEK_LOCAL_TABLENAME
|
||||
samplesTableName = constants.SIGNOZ_SAMPLES_V4_AGG_30M_TABLENAME
|
||||
} else {
|
||||
// 12 hours aggregation for any query greater than 1 week
|
||||
step = 12 * 60 * 60
|
||||
timeSeriesTableName = constants.SIGNOZ_TIMESERIES_v4_1WEEK_LOCAL_TABLENAME
|
||||
samplesTableName = constants.SIGNOZ_SAMPLES_V4_AGG_30M_TABLENAME
|
||||
}
|
||||
return step, timeSeriesTableName, samplesTableName
|
||||
}
|
||||
|
||||
func getParamsForTopHosts(req model.HostListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopProcesses(req model.ProcessListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopPods(req model.PodListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopNodes(req model.NodeListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopNamespaces(req model.NamespaceListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopClusters(req model.ClusterListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopDeployments(req model.DeploymentListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopDaemonSets(req model.DaemonSetListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopStatefulSets(req model.StatefulSetListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopJobs(req model.JobListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
func getParamsForTopVolumes(req model.VolumeListRequest) (int64, string, string) {
|
||||
return getParamsForTopItems(req.Start, req.End)
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): remove this
|
||||
// What is happening here?
|
||||
// The `PrepareTimeseriesFilterQuery` uses the local time series table for sub-query because each fingerprint
|
||||
// goes to same shard.
|
||||
// However, in this case, we are interested in the attributes values across all the shards.
|
||||
// So, we replace the local time series table with the distributed time series table.
|
||||
// See `PrepareTimeseriesFilterQuery` for more details.
|
||||
func localQueryToDistributedQuery(query string) string {
|
||||
return strings.Replace(query, ".time_series_v4", ".distributed_time_series_v4", 1)
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForDaemonSets = "k8s.pod.cpu.usage"
|
||||
k8sDaemonSetNameAttrKey = "k8s.daemonset.name"
|
||||
|
||||
metricNamesForDaemonSets = map[string]string{
|
||||
"desired_nodes": "k8s.daemonset.desired_scheduled_nodes",
|
||||
"available_nodes": "k8s.daemonset.current_scheduled_nodes",
|
||||
}
|
||||
|
||||
daemonSetAttrsToEnrich = []string{
|
||||
"k8s.daemonset.name",
|
||||
"k8s.namespace.name",
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
queryNamesForDaemonSets = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"cpu_request": {"B", "A"},
|
||||
"cpu_limit": {"C", "A"},
|
||||
"memory": {"D"},
|
||||
"memory_request": {"E", "D"},
|
||||
"memory_limit": {"F", "D"},
|
||||
"restarts": {"G", "A"},
|
||||
"desired_nodes": {"H"},
|
||||
"available_nodes": {"I"},
|
||||
}
|
||||
|
||||
builderQueriesForDaemonSets = map[string]*v3.BuilderQuery{
|
||||
// desired nodes
|
||||
"H": {
|
||||
QueryName: "H",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForDaemonSets["desired_nodes"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "H",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// available nodes
|
||||
"I": {
|
||||
QueryName: "I",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForDaemonSets["available_nodes"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "I",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
daemonSetQueryNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I"}
|
||||
)
|
||||
|
||||
type DaemonSetsRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewDaemonSetsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *DaemonSetsRepo {
|
||||
return &DaemonSetsRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (d *DaemonSetsRepo) GetDaemonSetAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any pod metric
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForDaemonSets
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := d.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (d *DaemonSetsRepo) GetDaemonSetAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForDaemonSets
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := d.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (d *DaemonSetsRepo) getMetadataAttributes(ctx context.Context, req model.DaemonSetListRequest) (map[string]map[string]string, error) {
|
||||
daemonSetAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range daemonSetAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForDaemonSets,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := d.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
daemonSetName := stringData[k8sDaemonSetNameAttrKey]
|
||||
if _, ok := daemonSetAttrs[daemonSetName]; !ok {
|
||||
daemonSetAttrs[daemonSetName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
daemonSetAttrs[daemonSetName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return daemonSetAttrs, nil
|
||||
}
|
||||
|
||||
func (d *DaemonSetsRepo) getTopDaemonSetGroups(ctx context.Context, orgID valuer.UUID, req model.DaemonSetListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopDaemonSets(req)
|
||||
|
||||
queryNames := queryNamesForDaemonSets[req.OrderBy.ColumnName]
|
||||
topDaemonSetGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topDaemonSetGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopDaemonSetGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topDaemonSetGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topDaemonSetGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopDaemonSetGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topDaemonSetGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopDaemonSetGroupsSeries {
|
||||
topDaemonSetGroups = append(topDaemonSetGroups, series.Labels)
|
||||
}
|
||||
allDaemonSetGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allDaemonSetGroups = append(allDaemonSetGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topDaemonSetGroups, allDaemonSetGroups, nil
|
||||
}
|
||||
|
||||
func (d *DaemonSetsRepo) GetDaemonSetList(ctx context.Context, orgID valuer.UUID, req model.DaemonSetListRequest) (model.DaemonSetListResponse, error) {
|
||||
resp := model.DaemonSetListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sDaemonSetNameAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := WorkloadTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
// add additional queries for daemon sets
|
||||
for _, daemonSetQuery := range builderQueriesForDaemonSets {
|
||||
query.CompositeQuery.BuilderQueries[daemonSetQuery.QueryName] = daemonSetQuery.Clone()
|
||||
}
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
// make sure we only get records for daemon sets
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: k8sDaemonSetNameAttrKey},
|
||||
Operator: v3.FilterOperatorExists,
|
||||
})
|
||||
}
|
||||
|
||||
daemonSetAttrs, err := d.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topDaemonSetGroups, allDaemonSetGroups, err := d.getTopDaemonSetGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topDaemonSetGroup := range topDaemonSetGroups {
|
||||
for k, v := range topDaemonSetGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetDaemonSetList",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.DaemonSetListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.DaemonSetListRecord{
|
||||
DaemonSetName: "",
|
||||
CPUUsage: -1,
|
||||
CPURequest: -1,
|
||||
CPULimit: -1,
|
||||
MemoryUsage: -1,
|
||||
MemoryRequest: -1,
|
||||
MemoryLimit: -1,
|
||||
DesiredNodes: -1,
|
||||
AvailableNodes: -1,
|
||||
}
|
||||
|
||||
if daemonSetName, ok := row.Data[k8sDaemonSetNameAttrKey].(string); ok {
|
||||
record.DaemonSetName = daemonSetName
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.CPUUsage = cpu
|
||||
}
|
||||
if cpuRequest, ok := row.Data["B"].(float64); ok {
|
||||
record.CPURequest = cpuRequest
|
||||
}
|
||||
|
||||
if cpuLimit, ok := row.Data["C"].(float64); ok {
|
||||
record.CPULimit = cpuLimit
|
||||
}
|
||||
|
||||
if memory, ok := row.Data["D"].(float64); ok {
|
||||
record.MemoryUsage = memory
|
||||
}
|
||||
|
||||
if memoryRequest, ok := row.Data["E"].(float64); ok {
|
||||
record.MemoryRequest = memoryRequest
|
||||
}
|
||||
|
||||
if memoryLimit, ok := row.Data["F"].(float64); ok {
|
||||
record.MemoryLimit = memoryLimit
|
||||
}
|
||||
|
||||
if restarts, ok := row.Data["G"].(float64); ok {
|
||||
record.Restarts = int(restarts)
|
||||
}
|
||||
|
||||
if desiredNodes, ok := row.Data["H"].(float64); ok {
|
||||
record.DesiredNodes = int(desiredNodes)
|
||||
}
|
||||
|
||||
if availableNodes, ok := row.Data["I"].(float64); ok {
|
||||
record.AvailableNodes = int(availableNodes)
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := daemonSetAttrs[record.DaemonSetName]; ok && record.DaemonSetName != "" {
|
||||
record.Meta = daemonSetAttrs[record.DaemonSetName]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(daemonSetQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allDaemonSetGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForDeployments = "k8s.pod.cpu.usage"
|
||||
k8sDeploymentNameAttrKey = "k8s.deployment.name"
|
||||
|
||||
metricNamesForDeployments = map[string]string{
|
||||
"desired_pods": "k8s.deployment.desired",
|
||||
"available_pods": "k8s.deployment.available",
|
||||
}
|
||||
|
||||
deploymentAttrsToEnrich = []string{
|
||||
"k8s.deployment.name",
|
||||
"k8s.namespace.name",
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
queryNamesForDeployments = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"cpu_request": {"B", "A"},
|
||||
"cpu_limit": {"C", "A"},
|
||||
"memory": {"D"},
|
||||
"memory_request": {"E", "D"},
|
||||
"memory_limit": {"F", "D"},
|
||||
"restarts": {"G", "A"},
|
||||
"desired_pods": {"H"},
|
||||
"available_pods": {"I"},
|
||||
}
|
||||
|
||||
builderQueriesForDeployments = map[string]*v3.BuilderQuery{
|
||||
// desired pods
|
||||
"H": {
|
||||
QueryName: "H",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForDeployments["desired_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "H",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// available pods
|
||||
"I": {
|
||||
QueryName: "I",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForDeployments["available_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "I",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
deploymentQueryNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I"}
|
||||
)
|
||||
|
||||
type DeploymentsRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewDeploymentsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *DeploymentsRepo {
|
||||
return &DeploymentsRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (d *DeploymentsRepo) GetDeploymentAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any pod metric
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForDeployments
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := d.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (d *DeploymentsRepo) GetDeploymentAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForDeployments
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := d.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (d *DeploymentsRepo) getMetadataAttributes(ctx context.Context, req model.DeploymentListRequest) (map[string]map[string]string, error) {
|
||||
deploymentAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range deploymentAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForDeployments,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := d.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
deploymentName := stringData[k8sDeploymentNameAttrKey]
|
||||
if _, ok := deploymentAttrs[deploymentName]; !ok {
|
||||
deploymentAttrs[deploymentName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
deploymentAttrs[deploymentName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return deploymentAttrs, nil
|
||||
}
|
||||
|
||||
func (d *DeploymentsRepo) getTopDeploymentGroups(ctx context.Context, orgID valuer.UUID, req model.DeploymentListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopDeployments(req)
|
||||
|
||||
queryNames := queryNamesForDeployments[req.OrderBy.ColumnName]
|
||||
topDeploymentGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topDeploymentGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopDeploymentGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topDeploymentGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topDeploymentGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopDeploymentGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topDeploymentGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopDeploymentGroupsSeries {
|
||||
topDeploymentGroups = append(topDeploymentGroups, series.Labels)
|
||||
}
|
||||
allDeploymentGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allDeploymentGroups = append(allDeploymentGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topDeploymentGroups, allDeploymentGroups, nil
|
||||
}
|
||||
|
||||
func (d *DeploymentsRepo) GetDeploymentList(ctx context.Context, orgID valuer.UUID, req model.DeploymentListRequest) (model.DeploymentListResponse, error) {
|
||||
resp := model.DeploymentListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sDeploymentNameAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := WorkloadTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
// add additional queries for deployments
|
||||
for _, deploymentQuery := range builderQueriesForDeployments {
|
||||
query.CompositeQuery.BuilderQueries[deploymentQuery.QueryName] = deploymentQuery.Clone()
|
||||
}
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
// make sure we only get records for deployments
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: k8sDeploymentNameAttrKey},
|
||||
Operator: v3.FilterOperatorExists,
|
||||
})
|
||||
}
|
||||
|
||||
deploymentAttrs, err := d.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topDeploymentGroups, allDeploymentGroups, err := d.getTopDeploymentGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topDeploymentGroup := range topDeploymentGroups {
|
||||
for k, v := range topDeploymentGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.DeploymentListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.DeploymentListRecord{
|
||||
DeploymentName: "",
|
||||
CPUUsage: -1,
|
||||
CPURequest: -1,
|
||||
CPULimit: -1,
|
||||
MemoryUsage: -1,
|
||||
MemoryRequest: -1,
|
||||
MemoryLimit: -1,
|
||||
DesiredPods: -1,
|
||||
AvailablePods: -1,
|
||||
}
|
||||
|
||||
if deploymentName, ok := row.Data[k8sDeploymentNameAttrKey].(string); ok {
|
||||
record.DeploymentName = deploymentName
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.CPUUsage = cpu
|
||||
}
|
||||
if cpuRequest, ok := row.Data["B"].(float64); ok {
|
||||
record.CPURequest = cpuRequest
|
||||
}
|
||||
|
||||
if cpuLimit, ok := row.Data["C"].(float64); ok {
|
||||
record.CPULimit = cpuLimit
|
||||
}
|
||||
|
||||
if memory, ok := row.Data["D"].(float64); ok {
|
||||
record.MemoryUsage = memory
|
||||
}
|
||||
|
||||
if memoryRequest, ok := row.Data["E"].(float64); ok {
|
||||
record.MemoryRequest = memoryRequest
|
||||
}
|
||||
|
||||
if memoryLimit, ok := row.Data["F"].(float64); ok {
|
||||
record.MemoryLimit = memoryLimit
|
||||
}
|
||||
|
||||
if restarts, ok := row.Data["G"].(float64); ok {
|
||||
record.Restarts = int(restarts)
|
||||
}
|
||||
|
||||
if desiredPods, ok := row.Data["H"].(float64); ok {
|
||||
record.DesiredPods = int(desiredPods)
|
||||
}
|
||||
|
||||
if availablePods, ok := row.Data["I"].(float64); ok {
|
||||
record.AvailablePods = int(availablePods)
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := deploymentAttrs[record.DeploymentName]; ok && record.DeploymentName != "" {
|
||||
record.Meta = deploymentAttrs[record.DeploymentName]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(deploymentQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allDeploymentGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,550 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type HostsRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
var (
|
||||
// we don't have a way to get the resource attributes from the current time series table
|
||||
// but we only want to suggest resource attributes for system metrics,
|
||||
// this is a list of attributes that we skip from all labels as they are data point attributes
|
||||
// TODO(srikanthccv): remove this once we have a way to get resource attributes
|
||||
|
||||
pointAttrsToIgnore = []string{
|
||||
"state",
|
||||
"cpu",
|
||||
"device",
|
||||
"direction",
|
||||
"mode",
|
||||
"mountpoint",
|
||||
"type",
|
||||
"os.type",
|
||||
"process.cgroup",
|
||||
"process.command",
|
||||
"process.command_line",
|
||||
"process.executable.name",
|
||||
"process.executable.path",
|
||||
"process.owner",
|
||||
"process.parent_pid",
|
||||
"process.pid",
|
||||
}
|
||||
|
||||
queryNamesForTopHosts = map[string][]string{
|
||||
"cpu": {"A", "B", "F1"},
|
||||
"memory": {"C", "D", "F2"},
|
||||
"wait": {"E", "F", "F3"},
|
||||
"load15": {"G"},
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any system metric
|
||||
metricToUseForHostAttributes = "system.cpu.load_average.15m"
|
||||
hostNameAttrKey = "host.name"
|
||||
agentNameToIgnore = "k8s-infra-otel-agent"
|
||||
hostAttrsToEnrich = []string{
|
||||
"os.type",
|
||||
}
|
||||
metricNamesForHosts = map[string]string{
|
||||
"filesystem": "system.filesystem.usage",
|
||||
"cpu": "system.cpu.time",
|
||||
"memory": "system.memory.usage",
|
||||
"load15": "system.cpu.load_average.15m",
|
||||
"wait": "system.cpu.time",
|
||||
}
|
||||
uniqueMetricNamesForHosts = []string{
|
||||
"system.uptime",
|
||||
"system.cpu.time",
|
||||
"system.cpu.load_average.1m",
|
||||
"system.cpu.load_average.5m",
|
||||
"system.cpu.load_average.15m",
|
||||
"system.memory.usage",
|
||||
"system.paging.usage",
|
||||
"system.paging.faults",
|
||||
"system.paging.operations",
|
||||
"system.disk.io",
|
||||
"system.disk.operations",
|
||||
"system.disk.io_time",
|
||||
"system.disk.operation_time",
|
||||
"system.disk.merged",
|
||||
"system.disk.pending_operations",
|
||||
"system.disk.weighted_io_time",
|
||||
"system.filesystem.usage",
|
||||
"system.filesystem.inodes.usage",
|
||||
"system.network.io",
|
||||
"system.network.errors",
|
||||
"system.network.connections",
|
||||
"system.network.dropped",
|
||||
"system.network.packets",
|
||||
"system.processes.count",
|
||||
"system.processes.created",
|
||||
"process.cpu.time",
|
||||
"process.disk.io",
|
||||
"process.memory.usage",
|
||||
"process.memory.virtual",
|
||||
"nfs.client.net.count",
|
||||
"nfs.client.net.tcp.connection.accepted",
|
||||
"nfs.client.operation.count",
|
||||
"nfs.client.procedure.count",
|
||||
"nfs.client.rpc.authrefresh.count",
|
||||
"nfs.client.rpc.count",
|
||||
"nfs.client.rpc.retransmit.count",
|
||||
"nfs.server.fh.stale.count",
|
||||
"nfs.server.io",
|
||||
"nfs.server.net.count",
|
||||
"nfs.server.net.tcp.connection.accepted",
|
||||
"nfs.server.operation.count",
|
||||
"nfs.server.procedure.count",
|
||||
"nfs.server.repcache.requests",
|
||||
"nfs.server.rpc.count",
|
||||
"nfs.server.thread.count",
|
||||
}
|
||||
)
|
||||
|
||||
func NewHostsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *HostsRepo {
|
||||
return &HostsRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (h *HostsRepo) GetHostAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any system metric
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForHostAttributes
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := h.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (h *HostsRepo) GetHostAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForHostAttributes
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := h.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.FilterAttributeKey != hostNameAttrKey {
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
hostNames := []string{}
|
||||
|
||||
for _, attributeValue := range attributeValuesResponse.StringAttributeValues {
|
||||
if strings.Contains(attributeValue, agentNameToIgnore) {
|
||||
continue
|
||||
}
|
||||
hostNames = append(hostNames, attributeValue)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeValueResponse{StringAttributeValues: hostNames}, nil
|
||||
}
|
||||
|
||||
func (h *HostsRepo) getActiveHosts(ctx context.Context) (map[string]bool, error) {
|
||||
tenMinAgo := time.Now().Add(-10 * time.Minute).UTC().UnixMilli()
|
||||
return h.reader.GetActiveHostsFromMetricMetadata(ctx, uniqueMetricNamesForHosts, hostNameAttrKey, tenMinAgo)
|
||||
}
|
||||
|
||||
func (h *HostsRepo) getMetadataAttributes(ctx context.Context, req model.HostListRequest) (map[string]map[string]string, error) {
|
||||
hostAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range hostAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForHostAttributes,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := h.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
hostName := stringData[hostNameAttrKey]
|
||||
if _, ok := hostAttrs[hostName]; !ok {
|
||||
hostAttrs[hostName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
hostAttrs[hostName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return hostAttrs, nil
|
||||
}
|
||||
|
||||
func (h *HostsRepo) getTopHostGroups(ctx context.Context, orgID valuer.UUID, req model.HostListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopHosts(req)
|
||||
|
||||
queryNames := queryNamesForTopHosts[req.OrderBy.ColumnName]
|
||||
topHostGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topHostGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopHostGroups",
|
||||
})
|
||||
queryResponse, _, err := h.querierV2.QueryRange(ctx, orgID, topHostGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topHostGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopHostGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topHostGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopHostGroupsSeries {
|
||||
topHostGroups = append(topHostGroups, series.Labels)
|
||||
}
|
||||
allHostGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allHostGroups = append(allHostGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topHostGroups, allHostGroups, nil
|
||||
}
|
||||
|
||||
// GetHostMetricsExistenceAndEarliestTime returns (count, minFirstReportedUnixMilli, error) for host metrics
|
||||
// in distributed_metadata. Uses metricNamesForHosts plus system.filesystem.usage.
|
||||
func (h *HostsRepo) GetHostMetricsExistenceAndEarliestTime(ctx context.Context, req model.HostListRequest) (uint64, uint64, error) {
|
||||
names := []string{}
|
||||
for _, metricName := range metricNamesForHosts {
|
||||
names = append(names, metricName)
|
||||
}
|
||||
|
||||
return h.reader.GetMetricsExistenceAndEarliestTime(ctx, names)
|
||||
}
|
||||
|
||||
func (h *HostsRepo) IsSendingK8SAgentMetrics(ctx context.Context, req model.HostListRequest) ([]string, []string, error) {
|
||||
names := []string{}
|
||||
for _, metricName := range metricNamesForHosts {
|
||||
names = append(names, metricName)
|
||||
}
|
||||
namesStr := "'" + strings.Join(names, "','") + "'"
|
||||
|
||||
queryForRecentFingerprints := fmt.Sprintf(`
|
||||
SELECT DISTINCT fingerprint
|
||||
FROM %s.%s
|
||||
WHERE metric_name IN (%s)
|
||||
AND unix_milli >= toUnixTimestamp(now() - INTERVAL 5 MINUTE) * 1000`,
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_SAMPLES_V4_TABLENAME, namesStr)
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SELECT DISTINCT JSONExtractString(labels, '%s') as k8s_cluster_name, JSONExtractString(labels, '%s') as k8s_node_name
|
||||
FROM %s.%s
|
||||
WHERE metric_name IN (%s)
|
||||
AND unix_milli >= toUnixTimestamp(now() - INTERVAL 60 MINUTE) * 1000
|
||||
AND JSONExtractString(labels, '%s') LIKE '%%-otel-agent%%'
|
||||
AND fingerprint GLOBAL IN (%s)`,
|
||||
"k8s.cluster.name", "k8s.node.name",
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_V4_TABLENAME, namesStr, "host.name", queryForRecentFingerprints)
|
||||
|
||||
result, err := h.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
clusterNames := make(map[string]struct{})
|
||||
nodeNames := make(map[string]struct{})
|
||||
|
||||
for _, row := range result {
|
||||
switch v := row.Data["k8s.cluster.name"].(type) {
|
||||
case string:
|
||||
clusterNames[v] = struct{}{}
|
||||
case *string:
|
||||
clusterNames[*v] = struct{}{}
|
||||
}
|
||||
switch v := row.Data["k8s.node.name"].(type) {
|
||||
case string:
|
||||
nodeNames[v] = struct{}{}
|
||||
case *string:
|
||||
nodeNames[*v] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return maps.Keys(clusterNames), maps.Keys(nodeNames), nil
|
||||
}
|
||||
|
||||
func (h *HostsRepo) GetHostList(ctx context.Context, orgID valuer.UUID, req model.HostListRequest) (model.HostListResponse, error) {
|
||||
resp := model.HostListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
// default to cpu order by
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
// default to host name group by
|
||||
if len(req.GroupBy) == 0 {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: hostNameAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
// don't fail the request if we can't get these values
|
||||
if clusterNames, nodeNames, err := h.IsSendingK8SAgentMetrics(ctx, req); err == nil {
|
||||
resp.IsSendingK8SAgentMetrics = len(clusterNames) > 0 || len(nodeNames) > 0
|
||||
resp.ClusterNames = clusterNames
|
||||
resp.NodeNames = nodeNames
|
||||
}
|
||||
|
||||
// 1. Check if any host metrics exist and get earliest retention time
|
||||
// if no hosts metrics exist, that means we should show the onboarding guide on UI, and return early.
|
||||
// 2. If host metrics exist, but req.End is earlier than the earliest time of host metrics as read from
|
||||
// metadata table, then we should convey the same to the user and return early
|
||||
if count, minFirstReportedUnixMilli, err := h.GetHostMetricsExistenceAndEarliestTime(ctx, req); err == nil {
|
||||
if count == 0 {
|
||||
resp.SentAnyHostMetricsData = false
|
||||
resp.Records = []model.HostListRecord{}
|
||||
resp.Total = 0
|
||||
return resp, nil
|
||||
}
|
||||
resp.SentAnyHostMetricsData = true
|
||||
if req.End < int64(minFirstReportedUnixMilli) {
|
||||
resp.EndTimeBeforeRetention = true
|
||||
resp.Records = []model.HostListRecord{}
|
||||
resp.Total = 0
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
if step <= 0 {
|
||||
slog.ErrorContext(ctx, "step is less than or equal to 0", "step", step)
|
||||
return resp, errors.New("step is less than or equal to 0")
|
||||
}
|
||||
|
||||
query := HostsTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
}
|
||||
|
||||
hostAttrs, err := h.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
activeHosts, err := h.getActiveHosts(ctx)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topHostGroups, allHostGroups, err := h.getTopHostGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topHostGroup := range topHostGroups {
|
||||
for k, v := range topHostGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetHostList",
|
||||
})
|
||||
queryResponse, _, err := h.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.HostListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
record := model.HostListRecord{
|
||||
CPU: -1,
|
||||
Memory: -1,
|
||||
Wait: -1,
|
||||
Load15: -1,
|
||||
}
|
||||
|
||||
if hostName, ok := row.Data[hostNameAttrKey].(string); ok {
|
||||
record.HostName = hostName
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["F1"].(float64); ok {
|
||||
record.CPU = cpu
|
||||
}
|
||||
if memory, ok := row.Data["F2"].(float64); ok {
|
||||
record.Memory = memory
|
||||
}
|
||||
if wait, ok := row.Data["F3"].(float64); ok {
|
||||
record.Wait = wait
|
||||
}
|
||||
if load15, ok := row.Data["G"].(float64); ok {
|
||||
record.Load15 = load15
|
||||
}
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := hostAttrs[record.HostName]; ok {
|
||||
record.Meta = hostAttrs[record.HostName]
|
||||
}
|
||||
if osType, ok := record.Meta["os.type"]; ok {
|
||||
record.OS = osType
|
||||
}
|
||||
record.Active = activeHosts[record.HostName]
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allHostGroups)
|
||||
resp.Records = records
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
|
||||
var HostsTableListQuery = v3.QueryRangeParamsV3{
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForHosts["cpu"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "state",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotEqual,
|
||||
Value: "idle",
|
||||
},
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotContains,
|
||||
Value: agentNameToIgnore,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "A",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationRate,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: true,
|
||||
},
|
||||
"B": {
|
||||
QueryName: "B",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForHosts["cpu"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotContains,
|
||||
Value: agentNameToIgnore,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "B",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationRate,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: true,
|
||||
},
|
||||
"F1": {
|
||||
QueryName: "F1",
|
||||
Expression: "A/B",
|
||||
Legend: "CPU Usage (%)",
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
},
|
||||
"C": {
|
||||
QueryName: "C",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForHosts["memory"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "state",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: "used",
|
||||
},
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotContains,
|
||||
Value: agentNameToIgnore,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "C",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: true,
|
||||
},
|
||||
"D": {
|
||||
QueryName: "D",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForHosts["memory"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotContains,
|
||||
Value: agentNameToIgnore,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "D",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: true,
|
||||
},
|
||||
"F2": {
|
||||
QueryName: "F2",
|
||||
Expression: "C/D",
|
||||
Legend: "Memory Usage (%)",
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
},
|
||||
"E": {
|
||||
QueryName: "E",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForHosts["wait"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "state",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: "wait",
|
||||
},
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotContains,
|
||||
Value: agentNameToIgnore,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "E",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationRate,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: true,
|
||||
},
|
||||
"F": {
|
||||
QueryName: "F",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForHosts["wait"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotContains,
|
||||
Value: agentNameToIgnore,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "F",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationRate,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: true,
|
||||
},
|
||||
"F3": {
|
||||
QueryName: "F3",
|
||||
Expression: "E/F",
|
||||
Legend: "CPU Wait Time (%)",
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
},
|
||||
"G": {
|
||||
QueryName: "G",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForHosts["load15"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotContains,
|
||||
Value: agentNameToIgnore,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: hostNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "G",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
Legend: "CPU Load Average (15m)",
|
||||
},
|
||||
},
|
||||
PanelType: v3.PanelTypeTable,
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
},
|
||||
Version: "v4",
|
||||
FormatForWeb: true,
|
||||
}
|
||||
@@ -1,511 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForJobs = "k8s.job.desired_successful_pods"
|
||||
k8sJobNameAttrKey = "k8s.job.name"
|
||||
|
||||
metricNamesForJobs = map[string]string{
|
||||
"desired_successful_pods": "k8s.job.desired_successful_pods",
|
||||
"active_pods": "k8s.job.active_pods",
|
||||
"failed_pods": "k8s.job.failed_pods",
|
||||
"successful_pods": "k8s.job.successful_pods",
|
||||
}
|
||||
|
||||
jobAttrsToEnrich = []string{
|
||||
"k8s.job.name",
|
||||
"k8s.namespace.name",
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
queryNamesForJobs = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"cpu_request": {"B", "A"},
|
||||
"cpu_limit": {"C", "A"},
|
||||
"memory": {"D"},
|
||||
"memory_request": {"E", "D"},
|
||||
"memory_limit": {"F", "D"},
|
||||
"restarts": {"G", "A"},
|
||||
"desired_successful_pods": {"H"},
|
||||
"active_pods": {"I"},
|
||||
"failed_pods": {"J"},
|
||||
"successful_pods": {"K"},
|
||||
}
|
||||
|
||||
builderQueriesForJobs = map[string]*v3.BuilderQuery{
|
||||
// desired nodes
|
||||
"H": {
|
||||
QueryName: "H",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForJobs["desired_successful_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "H",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// available nodes
|
||||
"I": {
|
||||
QueryName: "I",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForJobs["active_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "I",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// failed pods
|
||||
"J": {
|
||||
QueryName: "J",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForJobs["failed_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "J",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// successful pods
|
||||
"K": {
|
||||
QueryName: "K",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForJobs["successful_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "K",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
jobQueryNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"}
|
||||
)
|
||||
|
||||
type JobsRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewJobsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *JobsRepo {
|
||||
return &JobsRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (d *JobsRepo) GetJobAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any pod metric
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForJobs
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := d.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (d *JobsRepo) GetJobAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForJobs
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := d.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (d *JobsRepo) getMetadataAttributes(ctx context.Context, req model.JobListRequest) (map[string]map[string]string, error) {
|
||||
jobAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range jobAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForJobs,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := d.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
jobName := stringData[k8sJobNameAttrKey]
|
||||
if _, ok := jobAttrs[jobName]; !ok {
|
||||
jobAttrs[jobName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
jobAttrs[jobName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return jobAttrs, nil
|
||||
}
|
||||
|
||||
func (d *JobsRepo) getTopJobGroups(ctx context.Context, orgID valuer.UUID, req model.JobListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopJobs(req)
|
||||
|
||||
queryNames := queryNamesForJobs[req.OrderBy.ColumnName]
|
||||
topJobGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topJobGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopJobGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topJobGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topJobGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopJobGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topJobGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopJobGroupsSeries {
|
||||
topJobGroups = append(topJobGroups, series.Labels)
|
||||
}
|
||||
allJobGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allJobGroups = append(allJobGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topJobGroups, allJobGroups, nil
|
||||
}
|
||||
|
||||
func (d *JobsRepo) GetJobList(ctx context.Context, orgID valuer.UUID, req model.JobListRequest) (model.JobListResponse, error) {
|
||||
resp := model.JobListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "desired_pods", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sJobNameAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := WorkloadTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
// add additional queries for jobs
|
||||
for _, jobQuery := range builderQueriesForJobs {
|
||||
query.CompositeQuery.BuilderQueries[jobQuery.QueryName] = jobQuery.Clone()
|
||||
}
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
// make sure we only get records for jobs
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: k8sJobNameAttrKey},
|
||||
Operator: v3.FilterOperatorExists,
|
||||
})
|
||||
}
|
||||
|
||||
jobAttrs, err := d.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topJobGroups, allJobGroups, err := d.getTopJobGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topJobGroup := range topJobGroups {
|
||||
for k, v := range topJobGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetJobList",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.JobListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.JobListRecord{
|
||||
JobName: "",
|
||||
CPUUsage: -1,
|
||||
CPURequest: -1,
|
||||
CPULimit: -1,
|
||||
MemoryUsage: -1,
|
||||
MemoryRequest: -1,
|
||||
MemoryLimit: -1,
|
||||
DesiredSuccessfulPods: -1,
|
||||
ActivePods: -1,
|
||||
FailedPods: -1,
|
||||
SuccessfulPods: -1,
|
||||
}
|
||||
|
||||
if jobName, ok := row.Data[k8sJobNameAttrKey].(string); ok {
|
||||
record.JobName = jobName
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.CPUUsage = cpu
|
||||
}
|
||||
if cpuRequest, ok := row.Data["B"].(float64); ok {
|
||||
record.CPURequest = cpuRequest
|
||||
}
|
||||
|
||||
if cpuLimit, ok := row.Data["C"].(float64); ok {
|
||||
record.CPULimit = cpuLimit
|
||||
}
|
||||
|
||||
if memory, ok := row.Data["D"].(float64); ok {
|
||||
record.MemoryUsage = memory
|
||||
}
|
||||
|
||||
if memoryRequest, ok := row.Data["E"].(float64); ok {
|
||||
record.MemoryRequest = memoryRequest
|
||||
}
|
||||
|
||||
if memoryLimit, ok := row.Data["F"].(float64); ok {
|
||||
record.MemoryLimit = memoryLimit
|
||||
}
|
||||
|
||||
if restarts, ok := row.Data["G"].(float64); ok {
|
||||
record.Restarts = int(restarts)
|
||||
}
|
||||
|
||||
if desiredSuccessfulPods, ok := row.Data["H"].(float64); ok {
|
||||
record.DesiredSuccessfulPods = int(desiredSuccessfulPods)
|
||||
}
|
||||
|
||||
if activePods, ok := row.Data["I"].(float64); ok {
|
||||
record.ActivePods = int(activePods)
|
||||
}
|
||||
|
||||
if failedPods, ok := row.Data["J"].(float64); ok {
|
||||
record.FailedPods = int(failedPods)
|
||||
}
|
||||
|
||||
if successfulPods, ok := row.Data["K"].(float64); ok {
|
||||
record.SuccessfulPods = int(successfulPods)
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := jobAttrs[record.JobName]; ok && record.JobName != "" {
|
||||
record.Meta = jobAttrs[record.JobName]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(jobQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allJobGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForNamespaces = "k8s.pod.cpu.usage"
|
||||
|
||||
namespaceAttrsToEnrich = []string{
|
||||
"k8s.namespace.name",
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
queryNamesForNamespaces = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"memory": {"D"},
|
||||
"pod_phase": {"H", "I", "J", "K"},
|
||||
}
|
||||
namespaceQueryNames = []string{"A", "D", "H", "I", "J", "K"}
|
||||
|
||||
attributesKeysForNamespaces = []v3.AttributeKey{
|
||||
{Key: "k8s.namespace.name"},
|
||||
{Key: "k8s.cluster.name"},
|
||||
}
|
||||
|
||||
k8sNamespaceNameAttrKey = "k8s.namespace.name"
|
||||
)
|
||||
|
||||
type NamespacesRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewNamespacesRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *NamespacesRepo {
|
||||
return &NamespacesRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (p *NamespacesRepo) GetNamespaceAttributeKeys(ctx context.Context, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: attributesKeysForNamespaces}, nil
|
||||
}
|
||||
|
||||
func (p *NamespacesRepo) GetNamespaceAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForNamespaces
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := p.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (p *NamespacesRepo) getMetadataAttributes(ctx context.Context, req model.NamespaceListRequest) (map[string]map[string]string, error) {
|
||||
namespaceAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range namespaceAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForNamespaces,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := p.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
namespaceName := stringData[k8sNamespaceNameAttrKey]
|
||||
if _, ok := namespaceAttrs[namespaceName]; !ok {
|
||||
namespaceAttrs[namespaceName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
namespaceAttrs[namespaceName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return namespaceAttrs, nil
|
||||
}
|
||||
|
||||
func (p *NamespacesRepo) getTopNamespaceGroups(ctx context.Context, orgID valuer.UUID, req model.NamespaceListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopNamespaces(req)
|
||||
|
||||
queryNames := queryNamesForNamespaces[req.OrderBy.ColumnName]
|
||||
topNamespaceGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topNamespaceGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopNamespaceGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topNamespaceGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topNamespaceGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopNamespaceGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topNamespaceGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopNamespaceGroupsSeries {
|
||||
topNamespaceGroups = append(topNamespaceGroups, series.Labels)
|
||||
}
|
||||
allNamespaceGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allNamespaceGroups = append(allNamespaceGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topNamespaceGroups, allNamespaceGroups, nil
|
||||
}
|
||||
|
||||
func (p *NamespacesRepo) GetNamespaceList(ctx context.Context, orgID valuer.UUID, req model.NamespaceListRequest) (model.NamespaceListResponse, error) {
|
||||
resp := model.NamespaceListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sNamespaceNameAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := PodsTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
for _, q := range query.CompositeQuery.BuilderQueries {
|
||||
|
||||
if !slices.Contains(namespaceQueryNames, q.QueryName) {
|
||||
delete(query.CompositeQuery.BuilderQueries, q.QueryName)
|
||||
}
|
||||
|
||||
q.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if q.Filters == nil {
|
||||
q.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
q.Filters.Items = append(q.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
q.GroupBy = req.GroupBy
|
||||
}
|
||||
|
||||
namespaceAttrs, err := p.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topNamespaceGroups, allNamespaceGroups, err := p.getTopNamespaceGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topNamespaceGroup := range topNamespaceGroups {
|
||||
for k, v := range topNamespaceGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetNamespaceList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.NamespaceListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.NamespaceListRecord{
|
||||
CPUUsage: -1,
|
||||
MemoryUsage: -1,
|
||||
}
|
||||
|
||||
if name, ok := row.Data[k8sNamespaceNameAttrKey].(string); ok {
|
||||
record.NamespaceName = name
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.CPUUsage = cpu
|
||||
}
|
||||
|
||||
if memory, ok := row.Data["D"].(float64); ok {
|
||||
record.MemoryUsage = memory
|
||||
}
|
||||
|
||||
if pending, ok := row.Data["H"].(float64); ok {
|
||||
record.CountByPhase.Pending = int(pending)
|
||||
}
|
||||
if running, ok := row.Data["I"].(float64); ok {
|
||||
record.CountByPhase.Running = int(running)
|
||||
}
|
||||
if succeeded, ok := row.Data["J"].(float64); ok {
|
||||
record.CountByPhase.Succeeded = int(succeeded)
|
||||
}
|
||||
if failed, ok := row.Data["K"].(float64); ok {
|
||||
record.CountByPhase.Failed = int(failed)
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := namespaceAttrs[record.NamespaceName]; ok && record.NamespaceName != "" {
|
||||
record.Meta = namespaceAttrs[record.NamespaceName]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(namespaceQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allNamespaceGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,387 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForNodes = "k8s.node.cpu.usage"
|
||||
|
||||
nodeAttrsToEnrich = []string{"k8s.node.name", "k8s.node.uid", "k8s.cluster.name"}
|
||||
|
||||
k8sNodeGroupAttrKey = "k8s.node.name"
|
||||
|
||||
queryNamesForNodes = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"cpu_allocatable": {"B"},
|
||||
"memory": {"C"},
|
||||
"memory_allocatable": {"D"},
|
||||
}
|
||||
nodeQueryNames = []string{"A", "B", "C", "D", "E", "F"}
|
||||
|
||||
metricNamesForNodes = map[string]string{
|
||||
"cpu": "k8s.node.cpu.usage",
|
||||
"cpu_allocatable": "k8s.node.allocatable_cpu",
|
||||
"memory": "k8s.node.memory.working_set",
|
||||
"memory_allocatable": "k8s.node.allocatable_memory",
|
||||
"node_condition": "k8s.node.condition_ready",
|
||||
}
|
||||
)
|
||||
|
||||
type NodesRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewNodesRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *NodesRepo {
|
||||
return &NodesRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (n *NodesRepo) GetNodeAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForNodes
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := n.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeKeysResponse, nil
|
||||
}
|
||||
|
||||
func (n *NodesRepo) DidSendNodeMetrics(ctx context.Context) (bool, error) {
|
||||
namesStr := "'" + strings.Join(nodeMetricNamesToCheck, "','") + "'"
|
||||
|
||||
query := fmt.Sprintf(didSendNodeMetricsQuery,
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_v4_1DAY_TABLENAME, namesStr)
|
||||
|
||||
count, err := n.reader.GetCountOfThings(ctx, query)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (n *NodesRepo) GetNodeAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForNodes
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := n.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (p *NodesRepo) getMetadataAttributes(ctx context.Context, req model.NodeListRequest) (map[string]map[string]string, error) {
|
||||
nodeAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range nodeAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForNodes,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := p.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
nodeUID := stringData[k8sNodeGroupAttrKey]
|
||||
if _, ok := nodeAttrs[nodeUID]; !ok {
|
||||
nodeAttrs[nodeUID] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
nodeAttrs[nodeUID][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return nodeAttrs, nil
|
||||
}
|
||||
|
||||
func (p *NodesRepo) getTopNodeGroups(ctx context.Context, orgID valuer.UUID, req model.NodeListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopNodes(req)
|
||||
|
||||
queryNames := queryNamesForNodes[req.OrderBy.ColumnName]
|
||||
topNodeGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topNodeGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopNodeGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topNodeGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topNodeGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
max := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopNodeGroupsSeries := formattedResponse[0].Series[req.Offset:int(max)]
|
||||
|
||||
topNodeGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopNodeGroupsSeries {
|
||||
topNodeGroups = append(topNodeGroups, series.Labels)
|
||||
}
|
||||
allNodeGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allNodeGroups = append(allNodeGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topNodeGroups, allNodeGroups, nil
|
||||
}
|
||||
|
||||
func (p *NodesRepo) GetNodeList(ctx context.Context, orgID valuer.UUID, req model.NodeListRequest) (model.NodeListResponse, error) {
|
||||
resp := model.NodeListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sNodeGroupAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := NodesTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
}
|
||||
|
||||
nodeAttrs, err := p.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topNodeGroups, allNodeGroups, err := p.getTopNodeGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topNodeGroup := range topNodeGroups {
|
||||
for k, v := range topNodeGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetNodeList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.NodeListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.NodeListRecord{
|
||||
NodeCPUUsage: -1,
|
||||
NodeCPUAllocatable: -1,
|
||||
NodeMemoryUsage: -1,
|
||||
NodeMemoryAllocatable: -1,
|
||||
}
|
||||
|
||||
if nodeUID, ok := row.Data[k8sNodeGroupAttrKey].(string); ok {
|
||||
record.NodeUID = nodeUID
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.NodeCPUUsage = cpu
|
||||
}
|
||||
|
||||
if cpuAllocatable, ok := row.Data["B"].(float64); ok {
|
||||
record.NodeCPUAllocatable = cpuAllocatable
|
||||
}
|
||||
|
||||
if mem, ok := row.Data["C"].(float64); ok {
|
||||
record.NodeMemoryUsage = mem
|
||||
}
|
||||
|
||||
if memory, ok := row.Data["D"].(float64); ok {
|
||||
record.NodeMemoryAllocatable = memory
|
||||
}
|
||||
|
||||
if ready, ok := row.Data["E"].(float64); ok {
|
||||
record.CountByCondition.Ready = int(ready)
|
||||
}
|
||||
|
||||
if notReady, ok := row.Data["F"].(float64); ok {
|
||||
record.CountByCondition.NotReady = int(notReady)
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := nodeAttrs[record.NodeUID]; ok && record.NodeUID != "" {
|
||||
record.Meta = nodeAttrs[record.NodeUID]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(nodeQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allNodeGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
|
||||
var NodesTableListQuery = v3.QueryRangeParamsV3{
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
// node cpu utilization
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForNodes["cpu"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "A",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// node cpu allocatable
|
||||
"B": {
|
||||
QueryName: "B",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForNodes["cpu_allocatable"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "B",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// node memory utilization
|
||||
"C": {
|
||||
QueryName: "C",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForNodes["memory"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "C",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// node memory allocatable
|
||||
"D": {
|
||||
QueryName: "D",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForNodes["memory_allocatable"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "D",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// node conditions - Ready
|
||||
"E": {
|
||||
QueryName: "E",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForNodes["node_condition"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "__value",
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "E",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// node conditions - NotReady
|
||||
"F": {
|
||||
QueryName: "F",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForNodes["node_condition"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "__value",
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "F",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
},
|
||||
PanelType: v3.PanelTypeTable,
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
},
|
||||
Version: "v4",
|
||||
FormatForWeb: true,
|
||||
}
|
||||
@@ -1,555 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForPods = "k8s.pod.cpu.usage"
|
||||
|
||||
podAttrsToEnrich = []string{
|
||||
"k8s.pod.uid",
|
||||
"k8s.pod.name",
|
||||
"k8s.namespace.name",
|
||||
"k8s.node.name",
|
||||
"k8s.deployment.name",
|
||||
"k8s.statefulset.name",
|
||||
"k8s.daemonset.name",
|
||||
"k8s.job.name",
|
||||
"k8s.cronjob.name",
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
k8sPodUIDAttrKey = "k8s.pod.uid"
|
||||
|
||||
queryNamesForPods = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"cpu_request": {"B", "A"},
|
||||
"cpu_limit": {"C", "A"},
|
||||
"memory": {"D"},
|
||||
"memory_request": {"E", "D"},
|
||||
"memory_limit": {"F", "D"},
|
||||
"restarts": {"G", "A"},
|
||||
"pod_phase": {"H", "I", "J", "K"},
|
||||
}
|
||||
podQueryNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"}
|
||||
|
||||
metricNamesForPods = map[string]string{
|
||||
"cpu": "k8s.pod.cpu.usage",
|
||||
"cpu_request": "k8s.pod.cpu_request_utilization",
|
||||
"cpu_limit": "k8s.pod.cpu_limit_utilization",
|
||||
"memory": "k8s.pod.memory.working_set",
|
||||
"memory_request": "k8s.pod.memory_request_utilization",
|
||||
"memory_limit": "k8s.pod.memory_limit_utilization",
|
||||
"restarts": "k8s.container.restarts",
|
||||
"pod_phase": "k8s.pod.phase",
|
||||
}
|
||||
)
|
||||
|
||||
type PodsRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewPodsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *PodsRepo {
|
||||
return &PodsRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (p *PodsRepo) GetPodAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any pod metric
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForPods
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := p.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) GetPodAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForPods
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := p.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) DidSendPodMetrics(ctx context.Context) (bool, error) {
|
||||
namesStr := "'" + strings.Join(podMetricNamesToCheck, "','") + "'"
|
||||
|
||||
query := fmt.Sprintf(didSendPodMetricsQuery,
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_v4_1DAY_TABLENAME, namesStr)
|
||||
|
||||
count, err := p.reader.GetCountOfThings(ctx, query)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) DidSendClusterMetrics(ctx context.Context) (bool, error) {
|
||||
namesStr := "'" + strings.Join(clusterMetricNamesToCheck, "','") + "'"
|
||||
|
||||
query := fmt.Sprintf(didSendClusterMetricsQuery,
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_v4_1DAY_TABLENAME, namesStr)
|
||||
|
||||
count, err := p.reader.GetCountOfThings(ctx, query)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) IsSendingOptionalPodMetrics(ctx context.Context) (bool, error) {
|
||||
namesStr := "'" + strings.Join(optionalPodMetricNamesToCheck, "','") + "'"
|
||||
|
||||
query := fmt.Sprintf(isSendingOptionalPodMetricsQuery,
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_v4_1DAY_TABLENAME, namesStr)
|
||||
|
||||
count, err := p.reader.GetCountOfThings(ctx, query)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) SendingRequiredMetadata(ctx context.Context) ([]model.PodOnboardingStatus, error) {
|
||||
namesStr := "'" + strings.Join(podMetricNamesToCheck, "','") + "'"
|
||||
|
||||
query := fmt.Sprintf(isSendingRequiredMetadataQuery,
|
||||
constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_V4_TABLENAME, namesStr)
|
||||
|
||||
result, err := p.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statuses := []model.PodOnboardingStatus{}
|
||||
|
||||
// for each pod, check if we have all the required metadata
|
||||
for _, row := range result {
|
||||
status := model.PodOnboardingStatus{}
|
||||
switch v := row.Data["k8s.cluster.name"].(type) {
|
||||
case string:
|
||||
status.HasClusterName = true
|
||||
status.ClusterName = v
|
||||
case *string:
|
||||
status.HasClusterName = *v != ""
|
||||
status.ClusterName = *v
|
||||
}
|
||||
switch v := row.Data["k8s.node.name"].(type) {
|
||||
case string:
|
||||
status.HasNodeName = true
|
||||
status.NodeName = v
|
||||
case *string:
|
||||
status.HasNodeName = *v != ""
|
||||
status.NodeName = *v
|
||||
}
|
||||
switch v := row.Data["k8s.namespace.name"].(type) {
|
||||
case string:
|
||||
status.HasNamespaceName = true
|
||||
status.NamespaceName = v
|
||||
case *string:
|
||||
status.HasNamespaceName = *v != ""
|
||||
status.NamespaceName = *v
|
||||
}
|
||||
switch v := row.Data["k8s.deployment.name"].(type) {
|
||||
case string:
|
||||
status.HasDeploymentName = true
|
||||
case *string:
|
||||
status.HasDeploymentName = *v != ""
|
||||
}
|
||||
switch v := row.Data["k8s.statefulset.name"].(type) {
|
||||
case string:
|
||||
status.HasStatefulsetName = true
|
||||
case *string:
|
||||
status.HasStatefulsetName = *v != ""
|
||||
}
|
||||
switch v := row.Data["k8s.daemonset.name"].(type) {
|
||||
case string:
|
||||
status.HasDaemonsetName = true
|
||||
case *string:
|
||||
status.HasDaemonsetName = *v != ""
|
||||
}
|
||||
switch v := row.Data["k8s.cronjob.name"].(type) {
|
||||
case string:
|
||||
status.HasCronjobName = true
|
||||
case *string:
|
||||
status.HasCronjobName = *v != ""
|
||||
}
|
||||
switch v := row.Data["k8s.job.name"].(type) {
|
||||
case string:
|
||||
status.HasJobName = true
|
||||
case *string:
|
||||
status.HasJobName = *v != ""
|
||||
}
|
||||
|
||||
switch v := row.Data["k8s.pod.name"].(type) {
|
||||
case string:
|
||||
status.PodName = v
|
||||
case *string:
|
||||
status.PodName = *v
|
||||
}
|
||||
|
||||
if !status.HasClusterName ||
|
||||
!status.HasNodeName ||
|
||||
!status.HasNamespaceName ||
|
||||
(!status.HasDeploymentName && !status.HasStatefulsetName && !status.HasDaemonsetName && !status.HasCronjobName && !status.HasJobName) {
|
||||
statuses = append(statuses, status)
|
||||
}
|
||||
}
|
||||
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) getMetadataAttributes(ctx context.Context, req model.PodListRequest) (map[string]map[string]string, error) {
|
||||
podAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range podAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForPods,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := p.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
podName := stringData[k8sPodUIDAttrKey]
|
||||
if _, ok := podAttrs[podName]; !ok {
|
||||
podAttrs[podName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
podAttrs[podName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return podAttrs, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) getTopPodGroups(ctx context.Context, orgID valuer.UUID, req model.PodListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopPods(req)
|
||||
|
||||
queryNames := queryNamesForPods[req.OrderBy.ColumnName]
|
||||
topPodGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topPodGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopPodGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topPodGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topPodGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopPodGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topPodGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopPodGroupsSeries {
|
||||
topPodGroups = append(topPodGroups, series.Labels)
|
||||
}
|
||||
allPodGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allPodGroups = append(allPodGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topPodGroups, allPodGroups, nil
|
||||
}
|
||||
|
||||
func (p *PodsRepo) GetPodList(ctx context.Context, orgID valuer.UUID, req model.PodListRequest) (model.PodListResponse, error) {
|
||||
resp := model.PodListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sPodUIDAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := PodsTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
}
|
||||
|
||||
podAttrs, err := p.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topPodGroups, allPodGroups, err := p.getTopPodGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topPodGroup := range topPodGroups {
|
||||
for k, v := range topPodGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetPodList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.PodListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.PodListRecord{
|
||||
PodCPU: -1,
|
||||
PodCPURequest: -1,
|
||||
PodCPULimit: -1,
|
||||
PodMemory: -1,
|
||||
PodMemoryRequest: -1,
|
||||
PodMemoryLimit: -1,
|
||||
RestartCount: -1,
|
||||
}
|
||||
|
||||
if podUID, ok := row.Data[k8sPodUIDAttrKey].(string); ok {
|
||||
record.PodUID = podUID
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.PodCPU = cpu
|
||||
}
|
||||
if cpuRequest, ok := row.Data["B"].(float64); ok {
|
||||
record.PodCPURequest = cpuRequest
|
||||
}
|
||||
|
||||
if cpuLimit, ok := row.Data["C"].(float64); ok {
|
||||
record.PodCPULimit = cpuLimit
|
||||
}
|
||||
|
||||
if memory, ok := row.Data["D"].(float64); ok {
|
||||
record.PodMemory = memory
|
||||
}
|
||||
|
||||
if memoryRequest, ok := row.Data["E"].(float64); ok {
|
||||
record.PodMemoryRequest = memoryRequest
|
||||
}
|
||||
|
||||
if memoryLimit, ok := row.Data["F"].(float64); ok {
|
||||
record.PodMemoryLimit = memoryLimit
|
||||
}
|
||||
|
||||
if restarts, ok := row.Data["G"].(float64); ok {
|
||||
record.RestartCount = int(restarts)
|
||||
}
|
||||
|
||||
if pending, ok := row.Data["H"].(float64); ok {
|
||||
record.CountByPhase.Pending = int(pending)
|
||||
}
|
||||
|
||||
if running, ok := row.Data["I"].(float64); ok {
|
||||
record.CountByPhase.Running = int(running)
|
||||
}
|
||||
|
||||
if succeeded, ok := row.Data["J"].(float64); ok {
|
||||
record.CountByPhase.Succeeded = int(succeeded)
|
||||
}
|
||||
|
||||
if failed, ok := row.Data["K"].(float64); ok {
|
||||
record.CountByPhase.Failed = int(failed)
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := podAttrs[record.PodUID]; ok && record.PodUID != "" {
|
||||
record.Meta = podAttrs[record.PodUID]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(podQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allPodGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
|
||||
var PodsTableListQuery = v3.QueryRangeParamsV3{
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
// pod cpu utilization
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["cpu"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "A",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod cpu request utilization
|
||||
"B": {
|
||||
QueryName: "B",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["cpu_request"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "B",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod cpu limit utilization
|
||||
"C": {
|
||||
QueryName: "C",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["cpu_limit"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "C",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod memory utilization
|
||||
"D": {
|
||||
QueryName: "D",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["memory"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "D",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod memory request utilization
|
||||
"E": {
|
||||
QueryName: "E",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["memory_request"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "E",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod memory limit utilization
|
||||
"F": {
|
||||
QueryName: "F",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["memory_limit"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "F",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
"G": {
|
||||
QueryName: "G",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["restarts"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "G",
|
||||
ReduceTo: v3.ReduceToOperatorSum,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationMax,
|
||||
Functions: []v3.Function{{Name: v3.FunctionNameRunningDiff}},
|
||||
Disabled: false,
|
||||
},
|
||||
// pod phase pending
|
||||
"H": {
|
||||
QueryName: "H",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["pod_phase"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "__value",
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "H",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationCount,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod phase running
|
||||
"I": {
|
||||
QueryName: "I",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["pod_phase"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "__value",
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "I",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationCount,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod phase succeeded
|
||||
"J": {
|
||||
QueryName: "J",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["pod_phase"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "__value",
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "J",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationCount,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod phase failed
|
||||
"K": {
|
||||
QueryName: "K",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForPods["pod_phase"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "__value",
|
||||
},
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
Value: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPodUIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "K",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationCount,
|
||||
Disabled: false,
|
||||
},
|
||||
},
|
||||
PanelType: v3.PanelTypeTable,
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
},
|
||||
Version: "v4",
|
||||
FormatForWeb: true,
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
|
||||
var ProcessesTableListQuery = v3.QueryRangeParamsV3{
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForProcesses["cpu"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: processPIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "A",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationRate,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: true,
|
||||
},
|
||||
"F1": {
|
||||
QueryName: "F1",
|
||||
Expression: "A",
|
||||
Legend: "Process CPU Usage (%)",
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
},
|
||||
"C": {
|
||||
QueryName: "C",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForProcesses["memory"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: processPIDAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "C",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
},
|
||||
PanelType: v3.PanelTypeTable,
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
},
|
||||
Version: "v4",
|
||||
FormatForWeb: true,
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
queryNamesForTopProcesses = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"memory": {"C"},
|
||||
}
|
||||
|
||||
processPIDAttrKey = "process.pid"
|
||||
metricNamesForProcesses = map[string]string{
|
||||
"cpu": "process.cpu.time",
|
||||
"memory": "process.memory.usage",
|
||||
}
|
||||
metricToUseForProcessAttributes = "process.memory.usage"
|
||||
processNameAttrKey = "process.executable.name"
|
||||
processCMDAttrKey = "process.command"
|
||||
processCMDLineAttrKey = "process.command_line"
|
||||
)
|
||||
|
||||
type ProcessesRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewProcessesRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *ProcessesRepo {
|
||||
return &ProcessesRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (p *ProcessesRepo) GetProcessAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any system metric
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = "process.memory.usage"
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := p.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (p *ProcessesRepo) GetProcessAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = "process.memory.usage"
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := p.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (p *ProcessesRepo) getMetadataAttributes(ctx context.Context,
|
||||
req model.ProcessListRequest) (map[string]map[string]string, error) {
|
||||
processAttrs := map[string]map[string]string{}
|
||||
|
||||
keysToAdd := []string{"process.pid", "process.executable.name", "process.command", "process.command_line"}
|
||||
for _, key := range keysToAdd {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForProcessAttributes,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Cumulative,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := p.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
processID := stringData[processPIDAttrKey]
|
||||
if _, ok := processAttrs[processID]; !ok {
|
||||
processAttrs[processID] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
processAttrs[processID][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return processAttrs, nil
|
||||
}
|
||||
|
||||
func (p *ProcessesRepo) getTopProcessGroups(ctx context.Context, orgID valuer.UUID, req model.ProcessListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopProcesses(req)
|
||||
|
||||
queryNames := queryNamesForTopProcesses[req.OrderBy.ColumnName]
|
||||
topProcessGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topProcessGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopProcessGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topProcessGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topProcessGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopProcessGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topProcessGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopProcessGroupsSeries {
|
||||
topProcessGroups = append(topProcessGroups, series.Labels)
|
||||
}
|
||||
allProcessGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allProcessGroups = append(allProcessGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topProcessGroups, allProcessGroups, nil
|
||||
}
|
||||
|
||||
func (p *ProcessesRepo) GetProcessList(ctx context.Context, orgID valuer.UUID, req model.ProcessListRequest) (model.ProcessListResponse, error) {
|
||||
resp := model.ProcessListResponse{}
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
// default to cpu order by
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
// default to process pid group by
|
||||
if len(req.GroupBy) == 0 {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: processPIDAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := ProcessesTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
}
|
||||
|
||||
processAttrs, err := p.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topProcessGroups, allProcessGroups, err := p.getTopProcessGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topProcessGroup := range topProcessGroups {
|
||||
for k, v := range topProcessGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetProcessList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.ProcessListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
record := model.ProcessListRecord{
|
||||
ProcessCPU: -1,
|
||||
ProcessMemory: -1,
|
||||
}
|
||||
|
||||
pid, ok := row.Data[processPIDAttrKey].(string)
|
||||
if ok {
|
||||
record.ProcessID = pid
|
||||
}
|
||||
|
||||
processCPU, ok := row.Data["F1"].(float64)
|
||||
if ok {
|
||||
record.ProcessCPU = processCPU
|
||||
}
|
||||
|
||||
processMemory, ok := row.Data["C"].(float64)
|
||||
if ok {
|
||||
record.ProcessMemory = processMemory
|
||||
}
|
||||
record.Meta = processAttrs[record.ProcessID]
|
||||
record.ProcessName = record.Meta[processNameAttrKey]
|
||||
record.ProcessCMD = record.Meta[processCMDAttrKey]
|
||||
record.ProcessCMDLine = record.Meta[processCMDLineAttrKey]
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Total = len(allProcessGroups)
|
||||
resp.Records = records
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForVolumes = "k8s.volume.available"
|
||||
|
||||
volumeAttrsToEnrich = []string{
|
||||
"k8s.pod.uid",
|
||||
"k8s.pod.name",
|
||||
"k8s.namespace.name",
|
||||
"k8s.node.name",
|
||||
"k8s.statefulset.name",
|
||||
"k8s.cluster.name",
|
||||
"k8s.persistentvolumeclaim.name",
|
||||
}
|
||||
|
||||
k8sPersistentVolumeClaimNameAttrKey = "k8s.persistentvolumeclaim.name"
|
||||
|
||||
queryNamesForVolumes = map[string][]string{
|
||||
"available": {"A"},
|
||||
"capacity": {"B", "A"},
|
||||
"usage": {"F1", "B", "A"},
|
||||
"inodes": {"C", "A"},
|
||||
"inodes_free": {"D", "A"},
|
||||
"inodes_used": {"E", "A"},
|
||||
}
|
||||
|
||||
volumeQueryNames = []string{"A", "B", "C", "D", "E", "F1"}
|
||||
|
||||
metricNamesForVolumes = map[string]string{
|
||||
"available": "k8s.volume.available",
|
||||
"capacity": "k8s.volume.capacity",
|
||||
"inodes": "k8s.volume.inodes",
|
||||
"inodes_free": "k8s.volume.inodes.free",
|
||||
"inodes_used": "k8s.volume.inodes.used",
|
||||
}
|
||||
)
|
||||
|
||||
type PvcsRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewPvcsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *PvcsRepo {
|
||||
return &PvcsRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (p *PvcsRepo) GetPvcAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForVolumes
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := p.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (p *PvcsRepo) GetPvcAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForVolumes
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := p.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (p *PvcsRepo) getMetadataAttributes(ctx context.Context, req model.VolumeListRequest) (map[string]map[string]string, error) {
|
||||
volumeAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range volumeAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForVolumes,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := p.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
volumeName := stringData[k8sPersistentVolumeClaimNameAttrKey]
|
||||
if _, ok := volumeAttrs[volumeName]; !ok {
|
||||
volumeAttrs[volumeName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
volumeAttrs[volumeName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return volumeAttrs, nil
|
||||
}
|
||||
|
||||
func (p *PvcsRepo) getTopVolumeGroups(ctx context.Context, orgID valuer.UUID, req model.VolumeListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopVolumes(req)
|
||||
|
||||
queryNames := queryNamesForVolumes[req.OrderBy.ColumnName]
|
||||
topVolumeGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topVolumeGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopVolumeGroups",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, topVolumeGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topVolumeGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopVolumeGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topVolumeGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopVolumeGroupsSeries {
|
||||
topVolumeGroups = append(topVolumeGroups, series.Labels)
|
||||
}
|
||||
allVolumeGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allVolumeGroups = append(allVolumeGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topVolumeGroups, allVolumeGroups, nil
|
||||
}
|
||||
|
||||
func (p *PvcsRepo) GetPvcList(ctx context.Context, orgID valuer.UUID, req model.VolumeListRequest) (model.VolumeListResponse, error) {
|
||||
resp := model.VolumeListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "usage", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sPersistentVolumeClaimNameAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := PvcsTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
}
|
||||
|
||||
volumeAttrs, err := p.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topVolumeGroups, allVolumeGroups, err := p.getTopVolumeGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topVolumeGroup := range topVolumeGroups {
|
||||
for k, v := range topVolumeGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetPvcList",
|
||||
})
|
||||
queryResponse, _, err := p.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.VolumeListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.VolumeListRecord{
|
||||
VolumeUsage: -1,
|
||||
VolumeAvailable: -1,
|
||||
VolumeCapacity: -1,
|
||||
VolumeInodes: -1,
|
||||
VolumeInodesFree: -1,
|
||||
VolumeInodesUsed: -1,
|
||||
Meta: map[string]string{},
|
||||
}
|
||||
|
||||
if volumeName, ok := row.Data[k8sPersistentVolumeClaimNameAttrKey].(string); ok {
|
||||
record.PersistentVolumeClaimName = volumeName
|
||||
}
|
||||
|
||||
if volumeAvailable, ok := row.Data["A"].(float64); ok {
|
||||
record.VolumeAvailable = volumeAvailable
|
||||
}
|
||||
if volumeCapacity, ok := row.Data["B"].(float64); ok {
|
||||
record.VolumeCapacity = volumeCapacity
|
||||
}
|
||||
|
||||
if volumeInodes, ok := row.Data["C"].(float64); ok {
|
||||
record.VolumeInodes = volumeInodes
|
||||
}
|
||||
|
||||
if volumeInodesFree, ok := row.Data["D"].(float64); ok {
|
||||
record.VolumeInodesFree = volumeInodesFree
|
||||
}
|
||||
|
||||
if volumeInodesUsed, ok := row.Data["E"].(float64); ok {
|
||||
record.VolumeInodesUsed = volumeInodesUsed
|
||||
}
|
||||
|
||||
record.VolumeUsage = record.VolumeCapacity - record.VolumeAvailable
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := volumeAttrs[record.PersistentVolumeClaimName]; ok && record.PersistentVolumeClaimName != "" {
|
||||
record.Meta = volumeAttrs[record.PersistentVolumeClaimName]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(volumeQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allVolumeGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
|
||||
var PvcsTableListQuery = v3.QueryRangeParamsV3{
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
// k8s.volume.available
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForVolumes["available"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotEqual,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "A",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// k8s.volume.capacity
|
||||
"B": {
|
||||
QueryName: "B",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForVolumes["capacity"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotEqual,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "B",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
"F1": {
|
||||
QueryName: "F1",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
Expression: "B - A",
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
},
|
||||
// k8s.volume.inodes
|
||||
"C": {
|
||||
QueryName: "C",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForVolumes["inodes"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotEqual,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "C",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// k8s.volume.inodes_free
|
||||
"D": {
|
||||
QueryName: "D",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForVolumes["inodes_free"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotEqual,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "D",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// k8s.volume.inodes_used
|
||||
"E": {
|
||||
QueryName: "E",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForVolumes["inodes_used"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
Operator: v3.FilterOperatorNotEqual,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sPersistentVolumeClaimNameAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
},
|
||||
Expression: "E",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
},
|
||||
PanelType: v3.PanelTypeTable,
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
},
|
||||
Version: "v4",
|
||||
FormatForWeb: true,
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metrics/v4/helpers"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
metricToUseForStatefulSets = "k8s.pod.cpu.usage"
|
||||
k8sStatefulSetNameAttrKey = "k8s.statefulset.name"
|
||||
|
||||
metricNamesForStatefulSets = map[string]string{
|
||||
"desired_pods": "k8s.statefulset.desired_pods",
|
||||
"available_pods": "k8s.statefulset.current_pods",
|
||||
}
|
||||
|
||||
statefulSetAttrsToEnrich = []string{
|
||||
"k8s.statefulset.name",
|
||||
"k8s.namespace.name",
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
queryNamesForStatefulSets = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
"cpu_request": {"B", "A"},
|
||||
"cpu_limit": {"C", "A"},
|
||||
"memory": {"D"},
|
||||
"memory_request": {"E", "D"},
|
||||
"memory_limit": {"F", "D"},
|
||||
"restarts": {"G", "A"},
|
||||
"desired_pods": {"H"},
|
||||
"available_pods": {"I"},
|
||||
}
|
||||
|
||||
builderQueriesForStatefulSets = map[string]*v3.BuilderQuery{
|
||||
// desired pods
|
||||
"H": {
|
||||
QueryName: "H",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForStatefulSets["desired_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "H",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// available pods
|
||||
"I": {
|
||||
QueryName: "I",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForStatefulSets["available_pods"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "I",
|
||||
ReduceTo: v3.ReduceToOperatorLast,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
statefulSetQueryNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I"}
|
||||
)
|
||||
|
||||
type StatefulSetsRepo struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
}
|
||||
|
||||
func NewStatefulSetsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *StatefulSetsRepo {
|
||||
return &StatefulSetsRepo{reader: reader, querierV2: querierV2}
|
||||
}
|
||||
|
||||
func (d *StatefulSetsRepo) GetStatefulSetAttributeKeys(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error) {
|
||||
// TODO(srikanthccv): remove hardcoded metric name and support keys from any pod metric
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForStatefulSets
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeKeysResponse, err := d.reader.GetMetricAttributeKeys(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): only return resource attributes when we have a way to
|
||||
// distinguish between resource attributes and other attributes.
|
||||
filteredKeys := []v3.AttributeKey{}
|
||||
for _, key := range attributeKeysResponse.AttributeKeys {
|
||||
if slices.Contains(pointAttrsToIgnore, key.Key) {
|
||||
continue
|
||||
}
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
|
||||
return &v3.FilterAttributeKeyResponse{AttributeKeys: filteredKeys}, nil
|
||||
}
|
||||
|
||||
func (d *StatefulSetsRepo) GetStatefulSetAttributeValues(ctx context.Context, orgID valuer.UUID, req v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
req.DataSource = v3.DataSourceMetrics
|
||||
req.AggregateAttribute = metricToUseForStatefulSets
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 50
|
||||
}
|
||||
|
||||
attributeValuesResponse, err := d.reader.GetMetricAttributeValues(ctx, orgID, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attributeValuesResponse, nil
|
||||
}
|
||||
|
||||
func (d *StatefulSetsRepo) getMetadataAttributes(ctx context.Context, req model.StatefulSetListRequest) (map[string]map[string]string, error) {
|
||||
statefulSetAttrs := map[string]map[string]string{}
|
||||
|
||||
for _, key := range statefulSetAttrsToEnrich {
|
||||
hasKey := false
|
||||
for _, groupByKey := range req.GroupBy {
|
||||
if groupByKey.Key == key {
|
||||
hasKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasKey {
|
||||
req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
mq := v3.BuilderQuery{
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricToUseForStatefulSets,
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
|
||||
query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = localQueryToDistributedQuery(query)
|
||||
|
||||
attrsListResponse, err := d.reader.GetListResultV3(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range attrsListResponse {
|
||||
stringData := map[string]string{}
|
||||
for key, value := range row.Data {
|
||||
if str, ok := value.(string); ok {
|
||||
stringData[key] = str
|
||||
} else if strPtr, ok := value.(*string); ok {
|
||||
stringData[key] = *strPtr
|
||||
}
|
||||
}
|
||||
|
||||
statefulSetName := stringData[k8sStatefulSetNameAttrKey]
|
||||
if _, ok := statefulSetAttrs[statefulSetName]; !ok {
|
||||
statefulSetAttrs[statefulSetName] = map[string]string{}
|
||||
}
|
||||
|
||||
for _, key := range req.GroupBy {
|
||||
statefulSetAttrs[statefulSetName][key.Key] = stringData[key.Key]
|
||||
}
|
||||
}
|
||||
|
||||
return statefulSetAttrs, nil
|
||||
}
|
||||
|
||||
func (d *StatefulSetsRepo) getTopStatefulSetGroups(ctx context.Context, orgID valuer.UUID, req model.StatefulSetListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) {
|
||||
step, timeSeriesTableName, samplesTableName := getParamsForTopStatefulSets(req)
|
||||
|
||||
queryNames := queryNamesForStatefulSets[req.OrderBy.ColumnName]
|
||||
topStatefulSetGroupsQueryRangeParams := &v3.QueryRangeParamsV3{
|
||||
Start: req.Start,
|
||||
End: req.End,
|
||||
Step: step,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{},
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
PanelType: v3.PanelTypeTable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, queryName := range queryNames {
|
||||
query := q.CompositeQuery.BuilderQueries[queryName].Clone()
|
||||
query.StepInterval = step
|
||||
query.MetricTableHints = &v3.MetricTableHints{
|
||||
TimeSeriesTableName: timeSeriesTableName,
|
||||
SamplesTableName: samplesTableName,
|
||||
}
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
topStatefulSetGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "getTopStatefulSetGroups",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, topStatefulSetGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, topStatefulSetGroupsQueryRangeParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if req.OrderBy.Order == v3.DirectionDesc {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
} else {
|
||||
sort.Slice(formattedResponse[0].Series, func(i, j int) bool {
|
||||
return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value
|
||||
})
|
||||
}
|
||||
|
||||
limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series)))
|
||||
|
||||
paginatedTopStatefulSetGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)]
|
||||
|
||||
topStatefulSetGroups := []map[string]string{}
|
||||
for _, series := range paginatedTopStatefulSetGroupsSeries {
|
||||
topStatefulSetGroups = append(topStatefulSetGroups, series.Labels)
|
||||
}
|
||||
allStatefulSetGroups := []map[string]string{}
|
||||
for _, series := range formattedResponse[0].Series {
|
||||
allStatefulSetGroups = append(allStatefulSetGroups, series.Labels)
|
||||
}
|
||||
|
||||
return topStatefulSetGroups, allStatefulSetGroups, nil
|
||||
}
|
||||
|
||||
func (d *StatefulSetsRepo) GetStatefulSetList(ctx context.Context, orgID valuer.UUID, req model.StatefulSetListRequest) (model.StatefulSetListResponse, error) {
|
||||
resp := model.StatefulSetListResponse{}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.OrderBy == nil {
|
||||
req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc}
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sStatefulSetNameAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
}
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
|
||||
query := WorkloadTableListQuery.Clone()
|
||||
|
||||
query.Start = req.Start
|
||||
query.End = req.End
|
||||
query.Step = step
|
||||
|
||||
// add additional queries for stateful sets
|
||||
for _, statefulSetQuery := range builderQueriesForStatefulSets {
|
||||
query.CompositeQuery.BuilderQueries[statefulSetQuery.QueryName] = statefulSetQuery.Clone()
|
||||
}
|
||||
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.StepInterval = step
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
if query.Filters == nil {
|
||||
query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}
|
||||
}
|
||||
query.Filters.Items = append(query.Filters.Items, req.Filters.Items...)
|
||||
}
|
||||
query.GroupBy = req.GroupBy
|
||||
// make sure we only get records for daemon sets
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: k8sStatefulSetNameAttrKey},
|
||||
Operator: v3.FilterOperatorExists,
|
||||
})
|
||||
}
|
||||
|
||||
statefulSetAttrs, err := d.getMetadataAttributes(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
topStatefulSetGroups, allStatefulSetGroups, err := d.getTopStatefulSetGroups(ctx, orgID, req, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
groupFilters := map[string][]string{}
|
||||
for _, topStatefulSetGroup := range topStatefulSetGroups {
|
||||
for k, v := range topStatefulSetGroup {
|
||||
groupFilters[k] = append(groupFilters[k], v)
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, groupValues := range groupFilters {
|
||||
hasGroupFilter := false
|
||||
if req.Filters != nil && len(req.Filters.Items) > 0 {
|
||||
for _, filter := range req.Filters.Items {
|
||||
if filter.Key.Key == groupKey {
|
||||
hasGroupFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasGroupFilter {
|
||||
for _, query := range query.CompositeQuery.BuilderQueries {
|
||||
query.Filters.Items = append(query.Filters.Items, v3.FilterItem{
|
||||
Key: v3.AttributeKey{Key: groupKey},
|
||||
Value: groupValues,
|
||||
Operator: v3.FilterOperatorIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "inframetrics",
|
||||
instrumentationtypes.CodeFunctionName: "GetStatefulSetList",
|
||||
})
|
||||
queryResponse, _, err := d.querierV2.QueryRange(ctx, orgID, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
formattedResponse, err := postprocess.PostProcessResult(queryResponse, query)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
records := []model.StatefulSetListRecord{}
|
||||
|
||||
for _, result := range formattedResponse {
|
||||
for _, row := range result.Table.Rows {
|
||||
|
||||
record := model.StatefulSetListRecord{
|
||||
StatefulSetName: "",
|
||||
CPUUsage: -1,
|
||||
CPURequest: -1,
|
||||
CPULimit: -1,
|
||||
MemoryUsage: -1,
|
||||
MemoryRequest: -1,
|
||||
MemoryLimit: -1,
|
||||
DesiredPods: -1,
|
||||
AvailablePods: -1,
|
||||
}
|
||||
|
||||
if statefulSetName, ok := row.Data[k8sStatefulSetNameAttrKey].(string); ok {
|
||||
record.StatefulSetName = statefulSetName
|
||||
}
|
||||
|
||||
if cpu, ok := row.Data["A"].(float64); ok {
|
||||
record.CPUUsage = cpu
|
||||
}
|
||||
if cpuRequest, ok := row.Data["B"].(float64); ok {
|
||||
record.CPURequest = cpuRequest
|
||||
}
|
||||
|
||||
if cpuLimit, ok := row.Data["C"].(float64); ok {
|
||||
record.CPULimit = cpuLimit
|
||||
}
|
||||
|
||||
if memory, ok := row.Data["D"].(float64); ok {
|
||||
record.MemoryUsage = memory
|
||||
}
|
||||
|
||||
if memoryRequest, ok := row.Data["E"].(float64); ok {
|
||||
record.MemoryRequest = memoryRequest
|
||||
}
|
||||
|
||||
if memoryLimit, ok := row.Data["F"].(float64); ok {
|
||||
record.MemoryLimit = memoryLimit
|
||||
}
|
||||
|
||||
if restarts, ok := row.Data["G"].(float64); ok {
|
||||
record.Restarts = int(restarts)
|
||||
}
|
||||
|
||||
if desiredPods, ok := row.Data["H"].(float64); ok {
|
||||
record.DesiredPods = int(desiredPods)
|
||||
}
|
||||
|
||||
if availablePods, ok := row.Data["I"].(float64); ok {
|
||||
record.AvailablePods = int(availablePods)
|
||||
}
|
||||
|
||||
record.Meta = map[string]string{}
|
||||
if _, ok := statefulSetAttrs[record.StatefulSetName]; ok && record.StatefulSetName != "" {
|
||||
record.Meta = statefulSetAttrs[record.StatefulSetName]
|
||||
}
|
||||
|
||||
for k, v := range row.Data {
|
||||
if slices.Contains(statefulSetQueryNames, k) {
|
||||
continue
|
||||
}
|
||||
if labelValue, ok := v.(string); ok {
|
||||
record.Meta[k] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
resp.Total = len(allStatefulSetGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
package inframetrics
|
||||
|
||||
import v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
|
||||
var (
|
||||
metricNamesForWorkloads = map[string]string{
|
||||
"cpu": "k8s.pod.cpu.usage",
|
||||
"cpu_request": "k8s.pod.cpu_request_utilization",
|
||||
"cpu_limit": "k8s.pod.cpu_limit_utilization",
|
||||
"memory": "k8s.pod.memory.working_set",
|
||||
"memory_request": "k8s.pod.memory_request_utilization",
|
||||
"memory_limit": "k8s.pod.memory_limit_utilization",
|
||||
"restarts": "k8s.container.restarts",
|
||||
}
|
||||
)
|
||||
|
||||
var WorkloadTableListQuery = v3.QueryRangeParamsV3{
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
// pod cpu utilization
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForWorkloads["cpu"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "A",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod cpu request utilization
|
||||
"B": {
|
||||
QueryName: "B",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForWorkloads["cpu_request"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "B",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod cpu limit utilization
|
||||
"C": {
|
||||
QueryName: "C",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForWorkloads["cpu_limit"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "C",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod memory utilization
|
||||
"D": {
|
||||
QueryName: "D",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForWorkloads["memory"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "D",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationSum,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod memory request utilization
|
||||
"E": {
|
||||
QueryName: "E",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForWorkloads["memory_request"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "E",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
// pod memory limit utilization
|
||||
"F": {
|
||||
QueryName: "F",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForWorkloads["memory_limit"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "F",
|
||||
ReduceTo: v3.ReduceToOperatorAvg,
|
||||
TimeAggregation: v3.TimeAggregationAvg,
|
||||
SpaceAggregation: v3.SpaceAggregationAvg,
|
||||
Disabled: false,
|
||||
},
|
||||
"G": {
|
||||
QueryName: "G",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: metricNamesForWorkloads["restarts"],
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
Temporality: v3.Unspecified,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{},
|
||||
Expression: "G",
|
||||
ReduceTo: v3.ReduceToOperatorSum,
|
||||
TimeAggregation: v3.TimeAggregationAnyLast,
|
||||
SpaceAggregation: v3.SpaceAggregationMax,
|
||||
Functions: []v3.Function{{Name: v3.FunctionNameRunningDiff}},
|
||||
Disabled: false,
|
||||
},
|
||||
},
|
||||
PanelType: v3.PanelTypeTable,
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
},
|
||||
Version: "v4",
|
||||
FormatForWeb: true,
|
||||
}
|
||||
@@ -361,7 +361,7 @@ func generateAggregateClause(panelType v3.PanelType, start, end int64, aggOp v3.
|
||||
}
|
||||
}
|
||||
|
||||
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string) (string, error) {
|
||||
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string, useJSONBody bool) (string, error) {
|
||||
// timerange will be sent in epoch millisecond
|
||||
logsStart := utils.GetEpochNanoSecs(start)
|
||||
logsEnd := utils.GetEpochNanoSecs(end)
|
||||
@@ -405,6 +405,9 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
|
||||
if mq.AggregateOperator == v3.AggregateOperatorNoOp {
|
||||
// with noop any filter or different order by other than ts will use new table
|
||||
sqlSelect := constants.LogsSQLSelectV2
|
||||
if useJSONBody {
|
||||
sqlSelect = constants.LogsSQLSelectV2WithBodyJSON
|
||||
}
|
||||
queryTmpl := sqlSelect + "from signoz_logs.%s where %s%s order by %s"
|
||||
query := fmt.Sprintf(queryTmpl, DISTRIBUTED_LOGS_V2, timeFilter, filterSubQuery, orderBy)
|
||||
return query, nil
|
||||
@@ -517,7 +520,7 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
|
||||
return query, nil
|
||||
} else if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
||||
// give me just the group_by names (no values)
|
||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
|
||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.UseJSONBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -525,14 +528,14 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
|
||||
|
||||
return query, nil
|
||||
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
|
||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.UseJSONBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return query, nil
|
||||
}
|
||||
|
||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
|
||||
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.UseJSONBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -899,7 +899,7 @@ func Test_buildLogsQuery(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := buildLogsQuery(tt.args.panelType, tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.graphLimitQtype)
|
||||
got, err := buildLogsQuery(tt.args.panelType, tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.graphLimitQtype, false)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("buildLogsQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
||||
@@ -215,18 +215,18 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
|
||||
case v3.DataSourceLogs:
|
||||
// for ts query with limit replace it as it is already formed
|
||||
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
|
||||
limitQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit})
|
||||
limitQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, UseJSONBody: params.UseJSONBody})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
placeholderQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit})
|
||||
placeholderQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, UseJSONBody: params.UseJSONBody})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := fmt.Sprintf(placeholderQuery, limitQuery)
|
||||
queries[queryName] = query
|
||||
} else {
|
||||
queryString, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: ""})
|
||||
queryString, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: "", UseJSONBody: params.UseJSONBody})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -166,7 +166,6 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
||||
api.RegisterLogsRoutes(r, am)
|
||||
api.RegisterIntegrationRoutes(r, am)
|
||||
api.RegisterQueryRangeV3Routes(r, am)
|
||||
api.RegisterInfraMetricsRoutes(r, am)
|
||||
api.RegisterWebSocketPaths(r, am)
|
||||
api.RegisterQueryRangeV4Routes(r, am)
|
||||
api.RegisterMessagingQueuesRoutes(r, am)
|
||||
|
||||
@@ -196,13 +196,17 @@ const (
|
||||
"CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool," +
|
||||
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string," +
|
||||
"CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope "
|
||||
LogsSQLSelectV2 = "SELECT " +
|
||||
"timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, " +
|
||||
"attributes_string, " +
|
||||
logsSQLSelectV2Head = "SELECT " +
|
||||
"timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, "
|
||||
logsSQLSelectV2Tail = "attributes_string, " +
|
||||
"attributes_number, " +
|
||||
"attributes_bool, " +
|
||||
"resources_string, " +
|
||||
"scope_string "
|
||||
LogsSQLSelectV2 = logsSQLSelectV2Head + "body, " + logsSQLSelectV2Tail
|
||||
// Orgs on JSON bodies keep the body in body_v2 and have the body column written empty.
|
||||
// Selected as JSON so the response carries the same body object v5 returns.
|
||||
LogsSQLSelectV2WithBodyJSON = logsSQLSelectV2Head + "body_v2 as body, " + logsSQLSelectV2Tail
|
||||
TracesExplorerViewSQLSelectWithSubQuery = "(SELECT traceID, durationNano, " +
|
||||
"serviceName, name FROM %s.%s WHERE parentSpanID = '' AND %s ORDER BY durationNano DESC LIMIT 1 BY traceID"
|
||||
TracesExplorerViewSQLSelectBeforeSubQuery = "SELECT subQuery.serviceName as `subQuery.serviceName`, subQuery.name as `subQuery.name`, count() AS " +
|
||||
|
||||
@@ -95,11 +95,6 @@ type Reader interface {
|
||||
ReportQueryStartForProgressTracking(queryId string) (reportQueryFinished func(), apiErr *model.ApiError)
|
||||
SubscribeToQueryProgress(queryId string) (<-chan model.QueryProgress, func(), *model.ApiError)
|
||||
|
||||
GetCountOfThings(ctx context.Context, query string) (uint64, error)
|
||||
GetActiveHostsFromMetricMetadata(ctx context.Context, metricNames []string, hostNameAttr string, sinceUnixMilli int64) (map[string]bool, error)
|
||||
|
||||
GetMetricsExistenceAndEarliestTime(ctx context.Context, metricNames []string) (uint64, uint64, error)
|
||||
|
||||
//trace
|
||||
GetTraceFields(ctx context.Context) (*model.GetFieldsResponse, *model.ApiError)
|
||||
UpdateTraceField(ctx context.Context, field *model.UpdateField) *model.ApiError
|
||||
|
||||
@@ -1,757 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
)
|
||||
|
||||
type (
|
||||
ResponseType string
|
||||
)
|
||||
|
||||
const (
|
||||
ResponseTypeList ResponseType = "list"
|
||||
ResponseTypeGroupedList ResponseType = "grouped_list"
|
||||
)
|
||||
|
||||
type HostListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type HostListRecord struct {
|
||||
HostName string `json:"hostName"`
|
||||
Active bool `json:"active"`
|
||||
OS string `json:"os"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory float64 `json:"memory"`
|
||||
Wait float64 `json:"wait"`
|
||||
Load15 float64 `json:"load15"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type HostListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []HostListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
SentAnyHostMetricsData bool `json:"sentAnyHostMetricsData"`
|
||||
IsSendingK8SAgentMetrics bool `json:"isSendingK8SAgentMetrics"`
|
||||
ClusterNames []string `json:"clusterNames"`
|
||||
NodeNames []string `json:"nodeNames"`
|
||||
EndTimeBeforeRetention bool `json:"endTimeBeforeRetention"`
|
||||
}
|
||||
|
||||
func (r *HostListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPU > r.Records[j].CPU
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Memory > r.Records[j].Memory
|
||||
})
|
||||
case "load15":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Load15 > r.Records[j].Load15
|
||||
})
|
||||
case "wait":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Wait > r.Records[j].Wait
|
||||
})
|
||||
}
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type ProcessListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []ProcessListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type ProcessListRecord struct {
|
||||
ProcessName string `json:"processName"`
|
||||
ProcessID string `json:"processID"`
|
||||
ProcessCMD string `json:"processCMD"`
|
||||
ProcessCMDLine string `json:"processCMDLine"`
|
||||
ProcessCPU float64 `json:"processCPU"`
|
||||
ProcessMemory float64 `json:"processMemory"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type PodListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type PodListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []PodListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *PodListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodCPU > r.Records[j].PodCPU
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodCPURequest > r.Records[j].PodCPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodCPULimit > r.Records[j].PodCPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodMemory > r.Records[j].PodMemory
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodMemoryRequest > r.Records[j].PodMemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodMemoryLimit > r.Records[j].PodMemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].RestartCount > r.Records[j].RestartCount
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PodListRecord struct {
|
||||
PodUID string `json:"podUID,omitempty"`
|
||||
PodCPU float64 `json:"podCPU"`
|
||||
PodCPURequest float64 `json:"podCPURequest"`
|
||||
PodCPULimit float64 `json:"podCPULimit"`
|
||||
PodMemory float64 `json:"podMemory"`
|
||||
PodMemoryRequest float64 `json:"podMemoryRequest"`
|
||||
PodMemoryLimit float64 `json:"podMemoryLimit"`
|
||||
RestartCount int `json:"restartCount"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
CountByPhase PodCountByPhase `json:"countByPhase"`
|
||||
}
|
||||
|
||||
type PodCountByPhase struct {
|
||||
Pending int `json:"pending"`
|
||||
Running int `json:"running"`
|
||||
Succeeded int `json:"succeeded"`
|
||||
Failed int `json:"failed"`
|
||||
Unknown int `json:"unknown"`
|
||||
}
|
||||
|
||||
type NodeListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type NodeListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []NodeListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *NodeListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeCPUUsage > r.Records[j].NodeCPUUsage
|
||||
})
|
||||
case "cpu_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeCPUAllocatable > r.Records[j].NodeCPUAllocatable
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeMemoryUsage > r.Records[j].NodeMemoryUsage
|
||||
})
|
||||
case "memory_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeMemoryAllocatable > r.Records[j].NodeMemoryAllocatable
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type NodeCountByCondition struct {
|
||||
Ready int `json:"ready"`
|
||||
NotReady int `json:"notReady"`
|
||||
Unknown int `json:"unknown"`
|
||||
}
|
||||
|
||||
type NodeListRecord struct {
|
||||
NodeUID string `json:"nodeUID,omitempty"`
|
||||
NodeCPUUsage float64 `json:"nodeCPUUsage"`
|
||||
NodeCPUAllocatable float64 `json:"nodeCPUAllocatable"`
|
||||
NodeMemoryUsage float64 `json:"nodeMemoryUsage"`
|
||||
NodeMemoryAllocatable float64 `json:"nodeMemoryAllocatable"`
|
||||
CountByCondition NodeCountByCondition `json:"countByCondition"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type NamespaceListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type NamespaceListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []NamespaceListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *NamespaceListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "pod_phase":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CountByPhase.Pending > r.Records[j].CountByPhase.Pending
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type NamespaceListRecord struct {
|
||||
NamespaceName string `json:"namespaceName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
CountByPhase PodCountByPhase `json:"countByPhase"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type ClusterListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type ClusterListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []ClusterListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *ClusterListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUAllocatable > r.Records[j].CPUAllocatable
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryAllocatable > r.Records[j].MemoryAllocatable
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ClusterListRecord struct {
|
||||
ClusterUID string `json:"clusterUID"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
CPUAllocatable float64 `json:"cpuAllocatable"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
MemoryAllocatable float64 `json:"memoryAllocatable"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type DeploymentListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type DeploymentListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []DeploymentListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *DeploymentListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "desired_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredPods > r.Records[j].DesiredPods
|
||||
})
|
||||
case "available_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].AvailablePods > r.Records[j].AvailablePods
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DeploymentListRecord struct {
|
||||
DeploymentName string `json:"deploymentName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
DesiredPods int `json:"desiredPods"`
|
||||
AvailablePods int `json:"availablePods"`
|
||||
CPURequest float64 `json:"cpuRequest"`
|
||||
MemoryRequest float64 `json:"memoryRequest"`
|
||||
CPULimit float64 `json:"cpuLimit"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
Restarts int `json:"restarts"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type DaemonSetListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type DaemonSetListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []DaemonSetListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *DaemonSetListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
case "desired_nodes":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredNodes > r.Records[j].DesiredNodes
|
||||
})
|
||||
case "available_nodes":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].AvailableNodes > r.Records[j].AvailableNodes
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DaemonSetListRecord struct {
|
||||
DaemonSetName string `json:"daemonSetName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
CPURequest float64 `json:"cpuRequest"`
|
||||
MemoryRequest float64 `json:"memoryRequest"`
|
||||
CPULimit float64 `json:"cpuLimit"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
Restarts int `json:"restarts"`
|
||||
DesiredNodes int `json:"desiredNodes"`
|
||||
AvailableNodes int `json:"availableNodes"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type StatefulSetListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type StatefulSetListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []StatefulSetListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *StatefulSetListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
case "desired_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredPods > r.Records[j].DesiredPods
|
||||
})
|
||||
case "available_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].AvailablePods > r.Records[j].AvailablePods
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type StatefulSetListRecord struct {
|
||||
StatefulSetName string `json:"statefulSetName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
CPURequest float64 `json:"cpuRequest"`
|
||||
MemoryRequest float64 `json:"memoryRequest"`
|
||||
CPULimit float64 `json:"cpuLimit"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
Restarts int `json:"restarts"`
|
||||
DesiredPods int `json:"desiredPods"`
|
||||
AvailablePods int `json:"availablePods"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type JobListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type JobListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []JobListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *JobListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
case "desired_successful_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredSuccessfulPods > r.Records[j].DesiredSuccessfulPods
|
||||
})
|
||||
case "active_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].ActivePods > r.Records[j].ActivePods
|
||||
})
|
||||
case "failed_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].FailedPods > r.Records[j].FailedPods
|
||||
})
|
||||
case "successful_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].SuccessfulPods > r.Records[j].SuccessfulPods
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type JobListRecord struct {
|
||||
JobName string `json:"jobName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
CPURequest float64 `json:"cpuRequest"`
|
||||
MemoryRequest float64 `json:"memoryRequest"`
|
||||
CPULimit float64 `json:"cpuLimit"`
|
||||
MemoryLimit float64 `json:"memoryLimit"`
|
||||
Restarts int `json:"restarts"`
|
||||
DesiredSuccessfulPods int `json:"desiredSuccessfulPods"`
|
||||
ActivePods int `json:"activePods"`
|
||||
FailedPods int `json:"failedPods"`
|
||||
SuccessfulPods int `json:"successfulPods"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type VolumeListRequest struct {
|
||||
Start int64 `json:"start"` // epoch time in ms
|
||||
End int64 `json:"end"` // epoch time in ms
|
||||
Filters *v3.FilterSet `json:"filters"`
|
||||
GroupBy []v3.AttributeKey `json:"groupBy"`
|
||||
OrderBy *v3.OrderBy `json:"orderBy"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type VolumeListResponse struct {
|
||||
Type ResponseType `json:"type"`
|
||||
Records []VolumeListRecord `json:"records"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *VolumeListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "available":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeAvailable > r.Records[j].VolumeAvailable
|
||||
})
|
||||
case "capacity":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeCapacity > r.Records[j].VolumeCapacity
|
||||
})
|
||||
case "usage":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeUsage > r.Records[j].VolumeUsage
|
||||
})
|
||||
case "inodes":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeInodes > r.Records[j].VolumeInodes
|
||||
})
|
||||
case "inodes_free":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeInodesFree > r.Records[j].VolumeInodesFree
|
||||
})
|
||||
case "inodes_used":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeInodesUsed > r.Records[j].VolumeInodesUsed
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type VolumeListRecord struct {
|
||||
PersistentVolumeClaimName string `json:"persistentVolumeClaimName"`
|
||||
VolumeAvailable float64 `json:"volumeAvailable"`
|
||||
VolumeCapacity float64 `json:"volumeCapacity"`
|
||||
VolumeInodes float64 `json:"volumeInodes"`
|
||||
VolumeInodesFree float64 `json:"volumeInodesFree"`
|
||||
VolumeInodesUsed float64 `json:"volumeInodesUsed"`
|
||||
VolumeUsage float64 `json:"volumeUsage"`
|
||||
Meta map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
type PodOnboardingStatus struct {
|
||||
ClusterName string `json:"clusterName"`
|
||||
NodeName string `json:"nodeName"`
|
||||
NamespaceName string `json:"namespaceName"`
|
||||
PodName string `json:"podName"`
|
||||
HasClusterName bool `json:"hasClusterName"`
|
||||
HasNodeName bool `json:"hasNodeName"`
|
||||
HasNamespaceName bool `json:"hasNamespaceName"`
|
||||
HasDeploymentName bool `json:"hasDeploymentName"`
|
||||
HasStatefulsetName bool `json:"hasStatefulsetName"`
|
||||
HasDaemonsetName bool `json:"hasDaemonsetName"`
|
||||
HasCronjobName bool `json:"hasCronjobName"`
|
||||
HasJobName bool `json:"hasJobName"`
|
||||
}
|
||||
|
||||
type OnboardingStatus struct {
|
||||
DidSendPodMetrics bool `json:"didSendPodMetrics"`
|
||||
DidSendNodeMetrics bool `json:"didSendNodeMetrics"`
|
||||
DidSendClusterMetrics bool `json:"didSendClusterMetrics"`
|
||||
IsSendingOptionalPodMetrics bool `json:"isSendingOptionalPodMetrics"`
|
||||
IsSendingRequiredMetadata []PodOnboardingStatus `json:"isSendingRequiredMetadata"`
|
||||
}
|
||||
@@ -435,6 +435,8 @@ type QueryRangeParamsV3 struct {
|
||||
NoCache bool `json:"noCache"`
|
||||
Version string `json:"-"`
|
||||
FormatForWeb bool `json:"formatForWeb,omitempty"`
|
||||
// Resolved from the use_json_body feature flag by the handler, never sent by clients.
|
||||
UseJSONBody bool `json:"-"`
|
||||
}
|
||||
|
||||
func (q *QueryRangeParamsV3) Clone() *QueryRangeParamsV3 {
|
||||
@@ -450,6 +452,7 @@ func (q *QueryRangeParamsV3) Clone() *QueryRangeParamsV3 {
|
||||
NoCache: q.NoCache,
|
||||
Version: q.Version,
|
||||
FormatForWeb: q.FormatForWeb,
|
||||
UseJSONBody: q.UseJSONBody,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1469,4 +1472,5 @@ type MetricMetadataResponse struct {
|
||||
type QBOptions struct {
|
||||
GraphLimitQtype string
|
||||
IsLivetailQuery bool
|
||||
UseJSONBody bool
|
||||
}
|
||||
|
||||
@@ -469,6 +469,8 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// The `[*]` path is extracted per value, not as an Array(String) compared to a
|
||||
// scalar — ClickHouse rejects that outright (code 130).
|
||||
name: "IN operator with json search",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
@@ -479,7 +481,7 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
||||
Limit: 10,
|
||||
},
|
||||
expected: qbtypes.Statement{
|
||||
Query: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE ((JSONExtract(JSON_QUERY(body, '$.\"user_names\"[*]'), 'Array(String)') = ?) AND JSON_EXISTS(body, '$.\"user_names\"[*]')) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Query: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE ((JSON_VALUE(body, '$.\"user_names\"[*]') = ?) AND JSON_EXISTS(body, '$.\"user_names\"[*]')) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{"john_doe", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
Warnings: []string{querybuilder.NewKeyNotFoundWarning("user_names[*]")},
|
||||
},
|
||||
|
||||
@@ -94,7 +94,11 @@ func (c *conditionBuilder) conditionFor(
|
||||
}
|
||||
conditions := []string{}
|
||||
for _, value := range values {
|
||||
conditions = append(conditions, sb.E(fieldExpression, value))
|
||||
cond, err := c.conditionFor(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorEqual, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
return sb.Or(conditions...), nil
|
||||
case qbtypes.FilterOperatorNotIn:
|
||||
@@ -104,7 +108,11 @@ func (c *conditionBuilder) conditionFor(
|
||||
}
|
||||
conditions := []string{}
|
||||
for _, value := range values {
|
||||
conditions = append(conditions, sb.NE(fieldExpression, value))
|
||||
cond, err := c.conditionFor(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorNotEqual, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
return sb.And(conditions...), nil
|
||||
case qbtypes.FilterOperatorExists, qbtypes.FilterOperatorNotExists:
|
||||
|
||||
@@ -410,7 +410,11 @@ func (c *conditionBuilder) conditionForResolvedKey(
|
||||
// instead of using IN, we use `=` + `OR` to make use of index
|
||||
conditions := []string{}
|
||||
for _, value := range values {
|
||||
conditions = append(conditions, sb.E(fieldExpression, value))
|
||||
cond, err := c.conditionForResolvedKey(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorEqual, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
return sb.Or(conditions...), nil
|
||||
case qbtypes.FilterOperatorNotIn:
|
||||
@@ -421,7 +425,11 @@ func (c *conditionBuilder) conditionForResolvedKey(
|
||||
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
||||
conditions := []string{}
|
||||
for _, value := range values {
|
||||
conditions = append(conditions, sb.NE(fieldExpression, value))
|
||||
cond, err := c.conditionForResolvedKey(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorNotEqual, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
return sb.And(conditions...), nil
|
||||
|
||||
|
||||
@@ -905,3 +905,52 @@ func TestConditionForJSONBodySearch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IN on the body column routes each value back through the `=` path; the SQL it produces
|
||||
// must stay what the shared IN handling produced before, including for a mixed-type list.
|
||||
func TestConditionForBodyIn(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
values []any
|
||||
expectedSQL string
|
||||
expectedArgs []any
|
||||
}{
|
||||
{
|
||||
name: "strings",
|
||||
values: []any{"alpha", "beta"},
|
||||
expectedSQL: "(body = ? OR body = ?)",
|
||||
expectedArgs: []any{"alpha", "beta"},
|
||||
},
|
||||
{
|
||||
name: "mixed types are stringified before they reach the column",
|
||||
values: []any{"alpha", float64(1), true},
|
||||
expectedSQL: "(body = ? OR body = ? OR body = ?)",
|
||||
expectedArgs: []any{"alpha", "1", "true"},
|
||||
},
|
||||
}
|
||||
|
||||
fl := flaggertest.New(t)
|
||||
fm := NewFieldMapper(fl)
|
||||
conditionBuilder := NewConditionBuilder(fm, fl)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
key := telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body",
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
}
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select("1").From("t")
|
||||
cond, _, err := conditionBuilder.ConditionFor(context.Background(), valuer.UUID{}, 0, 0, &key,
|
||||
map[string][]*telemetrytypes.TelemetryFieldKey{key.Name: {&key}}, qbtypes.ConditionBuilderOptions{},
|
||||
qbtypes.FilterOperatorIn, tc.values, sb)
|
||||
require.NoError(t, err)
|
||||
sb.Where(cond...)
|
||||
|
||||
sql, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
assert.Contains(t, sql, tc.expectedSQL)
|
||||
assert.Equal(t, tc.expectedArgs, args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,11 @@ func (c *conditionBuilder) conditionFor(
|
||||
// instead of using IN, we use `=` + `OR` to make use of index
|
||||
conditions := []string{}
|
||||
for _, value := range values {
|
||||
conditions = append(conditions, sb.E(fieldExpression, value))
|
||||
cond, err := c.conditionFor(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorEqual, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
return sb.Or(conditions...), nil
|
||||
case qbtypes.FilterOperatorNotIn:
|
||||
@@ -146,7 +150,11 @@ func (c *conditionBuilder) conditionFor(
|
||||
// instead of using NOT IN, we use `!=` + `AND` to make use of index
|
||||
conditions := []string{}
|
||||
for _, value := range values {
|
||||
conditions = append(conditions, sb.NE(fieldExpression, value))
|
||||
cond, err := c.conditionFor(ctx, orgID, startNs, endNs, key, qbtypes.FilterOperatorNotEqual, value, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
return sb.And(conditions...), nil
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@ echo "Generating TypeScript parser..."
|
||||
mkdir -p frontend/src/parser
|
||||
|
||||
# Generate TypeScript parser
|
||||
antlr4 -Dlanguage=TypeScript -o frontend/src/parser grammar/FilterQuery.g4 -visitor
|
||||
(cd grammar && antlr4 -Dlanguage=TypeScript -o ../frontend/src/parser FilterQuery.g4 -visitor)
|
||||
|
||||
echo "TypeScript parser generation complete"
|
||||
|
||||
46
tests/fixtures/auth.py
vendored
46
tests/fixtures/auth.py
vendored
@@ -16,6 +16,7 @@ from wiremock.resources.mappings import (
|
||||
|
||||
from fixtures import reuse, types
|
||||
from fixtures.logger import setup_logger
|
||||
from fixtures.role import find_role_by_name
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
@@ -32,6 +33,7 @@ USER_VIEWER_EMAIL = "viewer@integration.test"
|
||||
USER_VIEWER_PASSWORD = "password123Z$"
|
||||
|
||||
USERS_BASE = "/api/v2/users"
|
||||
USER_ROLES_BASE = "/api/v2/user_roles"
|
||||
|
||||
|
||||
def _login(signoz: types.SigNoz, email: str, password: str) -> str:
|
||||
@@ -344,24 +346,38 @@ def create_active_user(
|
||||
password: str,
|
||||
name: str = "",
|
||||
) -> str:
|
||||
"""Invite a user and activate via resetPassword. Returns user ID."""
|
||||
"""Create a pending invite user and activate it by setting a password.
|
||||
|
||||
role is a managed role name, e.g. signoz-viewer. Returns the user ID.
|
||||
"""
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": email, "role": role, "name": name},
|
||||
signoz.self.host_configs["8080"].get(USERS_BASE),
|
||||
json={
|
||||
"email": email,
|
||||
"displayName": name,
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, role)}],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
user_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{USERS_BASE}/{user_id}/reset_password_tokens"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
invited_user = response.json()["data"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": password, "token": invited_user["token"]},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": password, "token": response.json()["data"]["token"]},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
return invited_user["id"]
|
||||
return user_id
|
||||
|
||||
|
||||
def find_user_by_email(signoz: types.SigNoz, token: str, email: str) -> dict:
|
||||
@@ -409,21 +425,21 @@ def change_user_role(
|
||||
|
||||
Role names should be managed role names (e.g. signoz-editor).
|
||||
"""
|
||||
# Get current roles to find the old role's ID
|
||||
# Get current roles to find the old role's user_role entry ID
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{USERS_BASE}/{user_id}/roles"),
|
||||
signoz.self.host_configs["8080"].get(f"{USERS_BASE}/{user_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
roles = response.json()["data"]
|
||||
user_roles = response.json()["data"]["userRoles"]
|
||||
|
||||
old_role_entry = next((r for r in roles if r["name"] == old_role), None)
|
||||
old_role_entry = next((ur for ur in user_roles if ur["role"]["name"] == old_role), None)
|
||||
assert old_role_entry is not None, f"User does not have role '{old_role}'"
|
||||
|
||||
# Remove old role
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{USERS_BASE}/{user_id}/roles/{old_role_entry['id']}"),
|
||||
signoz.self.host_configs["8080"].get(f"{USER_ROLES_BASE}/{old_role_entry['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
@@ -431,9 +447,9 @@ def change_user_role(
|
||||
|
||||
# Assign new role
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"{USERS_BASE}/{user_id}/roles"),
|
||||
json={"name": new_role},
|
||||
signoz.self.host_configs["8080"].get(USER_ROLES_BASE),
|
||||
json={"userId": user_id, "roleId": find_role_by_name(signoz, admin_token, new_role)},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
12
tests/fixtures/idp.py
vendored
12
tests/fixtures/idp.py
vendored
@@ -634,18 +634,6 @@ def get_oidc_domain(signoz: types.SigNoz, admin_token: str) -> dict:
|
||||
)
|
||||
|
||||
|
||||
def get_user_by_email(signoz: types.SigNoz, admin_token: str, email: str) -> dict:
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/user"),
|
||||
timeout=2,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
)
|
||||
return next(
|
||||
(user for user in response.json()["data"] if user["email"] == email),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
def perform_oidc_login(
|
||||
signoz: types.SigNoz, # pylint: disable=unused-argument
|
||||
idp: types.TestContainerIDP,
|
||||
|
||||
20
tests/fixtures/role.py
vendored
20
tests/fixtures/role.py
vendored
@@ -9,18 +9,14 @@ from fixtures import types
|
||||
from fixtures.fs import get_testdata_file_path
|
||||
|
||||
|
||||
@pytest.fixture(name="find_role_id", scope="function")
|
||||
def find_role_id(signoz: types.SigNoz) -> Callable[[str, str], str]:
|
||||
def _find(token: str, name: str) -> str:
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/roles"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
return next(r["id"] for r in resp.json()["data"] if r["name"] == name)
|
||||
|
||||
return _find
|
||||
def find_role_by_name(signoz: types.SigNoz, token: str, name: str) -> str:
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/roles"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
return next(r["id"] for r in resp.json()["data"] if r["name"] == name)
|
||||
|
||||
|
||||
@pytest.fixture(name="create_role", scope="function")
|
||||
|
||||
11
tests/fixtures/serviceaccount.py
vendored
11
tests/fixtures/serviceaccount.py
vendored
@@ -4,22 +4,13 @@ import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
from fixtures.role import find_role_by_name
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
SERVICE_ACCOUNT_BASE = "/api/v1/service_accounts"
|
||||
|
||||
|
||||
def find_role_by_name(signoz: types.SigNoz, token: str, name: str) -> str:
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/roles"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
return next(r["id"] for r in resp.json()["data"] if r["name"] == name)
|
||||
|
||||
|
||||
def create_service_account(signoz: types.SigNoz, token: str, name: str, role: str = "signoz-viewer") -> str:
|
||||
"""Create a service account, assign a role, and return its ID."""
|
||||
resp = requests.post(
|
||||
|
||||
@@ -13,12 +13,14 @@ from fixtures.auth import (
|
||||
USER_ADMIN_PASSWORD,
|
||||
add_license,
|
||||
assert_user_has_role,
|
||||
create_active_user,
|
||||
find_user_with_roles_by_email,
|
||||
)
|
||||
from fixtures.idp import (
|
||||
get_saml_domain,
|
||||
perform_saml_login,
|
||||
)
|
||||
from fixtures.role import find_role_by_name
|
||||
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
|
||||
|
||||
|
||||
@@ -509,8 +511,12 @@ def test_saml_sso_login_activates_pending_invite_user(
|
||||
|
||||
# Invite user as ADMIN
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": email, "role": "ADMIN", "name": "SAML SSO Pending User"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={
|
||||
"email": email,
|
||||
"displayName": "SAML SSO Pending User",
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, "signoz-admin")}],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
@@ -547,22 +553,14 @@ def test_saml_sso_deleted_user_gets_new_user_on_login(
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# --- Step 1: Invite and activate via password reset ---
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": email, "role": "EDITOR", "name": "SAML SSO Lifecycle User"},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
user_id = create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=email,
|
||||
role="signoz-editor",
|
||||
password="password123Z$",
|
||||
name="SAML SSO Lifecycle User",
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED
|
||||
user_id = response.json()["data"]["id"]
|
||||
reset_token = response.json()["data"]["token"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": "password123Z$", "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# --- Step 2: Soft delete via DB using API
|
||||
response = requests.delete(
|
||||
|
||||
@@ -17,6 +17,7 @@ from fixtures.idp import (
|
||||
get_oidc_domain,
|
||||
perform_oidc_login,
|
||||
)
|
||||
from fixtures.role import find_role_by_name
|
||||
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
|
||||
|
||||
|
||||
@@ -464,8 +465,12 @@ def test_oidc_sso_login_activates_pending_invite_user(
|
||||
|
||||
# Invite user as ADMIN
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": email, "role": "ADMIN", "name": "OIDC SSO Pending User"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={
|
||||
"email": email,
|
||||
"displayName": "OIDC SSO Pending User",
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, "signoz-admin")}],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ from fixtures.auth import (
|
||||
create_active_user,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.role import transaction_group
|
||||
from fixtures.role import find_role_by_name, transaction_group
|
||||
from fixtures.types import Operation, SigNoz, TestContainerDocker
|
||||
|
||||
V2_BASE_URL = "/api/v2/dashboards"
|
||||
@@ -64,8 +64,8 @@ def test_setup_managed_role_users(
|
||||
existing_emails = {user["email"] for user in response.json()["data"]}
|
||||
|
||||
for email, role, password, name in (
|
||||
(_EDITOR_EMAIL, "EDITOR", _EDITOR_PASSWORD, "dashboard authz editor"),
|
||||
(_VIEWER_EMAIL, "VIEWER", _VIEWER_PASSWORD, "dashboard authz viewer"),
|
||||
(_EDITOR_EMAIL, "signoz-editor", _EDITOR_PASSWORD, "dashboard authz editor"),
|
||||
(_VIEWER_EMAIL, "signoz-viewer", _VIEWER_PASSWORD, "dashboard authz viewer"),
|
||||
):
|
||||
if email not in existing_emails:
|
||||
create_active_user(signoz, admin_token, email=email, role=role, password=password, name=name)
|
||||
@@ -411,7 +411,7 @@ def test_setup_scoped_actor(
|
||||
],
|
||||
)
|
||||
|
||||
user_id = create_active_user(signoz, admin_token, email=_ACTOR_EMAIL, role="VIEWER", password=_ACTOR_PASSWORD, name="dashboard fga actor")
|
||||
user_id = create_active_user(signoz, admin_token, email=_ACTOR_EMAIL, role="signoz-viewer", password=_ACTOR_PASSWORD, name="dashboard fga actor")
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", _ACTOR_ROLE_NAME)
|
||||
|
||||
|
||||
@@ -469,10 +469,9 @@ def test_publish_and_unpublish_require_update_on_the_dashboard(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
actor_role_id = find_role_id(admin_token, _ACTOR_ROLE_NAME)
|
||||
actor_role_id = find_role_by_name(signoz, admin_token, _ACTOR_ROLE_NAME)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_BASE_URL}?limit={MAX_LIST_LIMIT}"),
|
||||
@@ -579,7 +578,6 @@ def test_dashboard_authz_cleanup(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
actor = find_user_by_email(signoz, admin_token, _ACTOR_EMAIL)
|
||||
@@ -600,7 +598,7 @@ def test_dashboard_authz_cleanup(
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, f"remove role from user: {response.text}"
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{find_role_id(admin_token, _ACTOR_ROLE_NAME)}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{find_role_by_name(signoz, admin_token, _ACTOR_ROLE_NAME)}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from wiremock.client import (
|
||||
)
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license, create_active_user
|
||||
from fixtures.gateway import (
|
||||
TEST_KEY_ID,
|
||||
common_gateway_headers,
|
||||
@@ -43,21 +43,13 @@ def test_create_editor_user(
|
||||
"""Invite and register an editor user for gateway API tests."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
invite_response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": GATEWAY_APIS_EDITOR_EMAIL, "role": "EDITOR"},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=GATEWAY_APIS_EDITOR_EMAIL,
|
||||
role="signoz-editor",
|
||||
password=GATEWAY_APIS_EDITOR_PASSWORD,
|
||||
)
|
||||
assert invite_response.status_code == HTTPStatus.CREATED
|
||||
reset_token = invite_response.json()["data"]["token"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": GATEWAY_APIS_EDITOR_PASSWORD, "token": reset_token},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,7 @@ from fixtures.auth import (
|
||||
find_user_with_roles_by_email,
|
||||
)
|
||||
from fixtures.logger import setup_logger
|
||||
from fixtures.role import find_role_by_name
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
@@ -82,31 +83,36 @@ def test_register(signoz: types.SigNoz, get_token: Callable[[str, str], str]) ->
|
||||
|
||||
def test_invite(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
# Generate an invite token for the editor user
|
||||
# Create the editor user as a pending invite
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": USER_EDITOR_EMAIL, "role": "EDITOR", "name": USER_EDITOR_NAME},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={
|
||||
"email": USER_EDITOR_EMAIL,
|
||||
"displayName": USER_EDITOR_NAME,
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, "signoz-editor")}],
|
||||
},
|
||||
timeout=2,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
invited_user = response.json()["data"]
|
||||
assert invited_user["email"] == USER_EDITOR_EMAIL
|
||||
assert invited_user["role"] == "EDITOR"
|
||||
user_id = response.json()["data"]["id"]
|
||||
|
||||
# Verify the user appears in the users list but as pending_invite status
|
||||
found_user = find_user_with_roles_by_email(signoz, admin_token, USER_EDITOR_EMAIL)
|
||||
assert found_user["status"] == "pending_invite"
|
||||
assert_user_has_role(found_user, "signoz-editor")
|
||||
|
||||
reset_token = invited_user["token"]
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/reset_password_tokens"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
# Reset the password to complete the invite flow (activates the user and also grants authz)
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": USER_EDITOR_PASSWORD, "token": reset_token},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": USER_EDITOR_PASSWORD, "token": response.json()["data"]["token"]},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
@@ -130,18 +136,28 @@ def test_revoke_invite(signoz: types.SigNoz, get_token: Callable[[str, str], str
|
||||
|
||||
# Invite the viewer user
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": USER_VIEWER_EMAIL, "role": "VIEWER"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={
|
||||
"email": USER_VIEWER_EMAIL,
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, "signoz-viewer")}],
|
||||
},
|
||||
timeout=2,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
invited_user = response.json()["data"]
|
||||
reset_token = invited_user["token"]
|
||||
user_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/reset_password_tokens"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
reset_token = response.json()["data"]["token"]
|
||||
|
||||
# Delete the pending invite user (revoke the invite)
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{invited_user['id']}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}"),
|
||||
timeout=2,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
)
|
||||
@@ -149,7 +165,7 @@ def test_revoke_invite(signoz: types.SigNoz, get_token: Callable[[str, str], str
|
||||
|
||||
# Try to use the reset token — should fail (user deleted)
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "password123Z$", "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
@@ -157,67 +173,83 @@ def test_revoke_invite(signoz: types.SigNoz, get_token: Callable[[str, str], str
|
||||
|
||||
|
||||
def test_provision_user(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
|
||||
"""
|
||||
Simulates the upstream zeus provisioning flow:
|
||||
1. Invite a user as ADMIN (register already happened via test_register)
|
||||
2. List users to find the invited user's ID
|
||||
3. Get reset password token for that user
|
||||
4. Use the token to set the password and activate the user
|
||||
5. Verify the user can log in
|
||||
"""
|
||||
"""Mirrors the zeus provisioning flow."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
provisioned_email = "zeus-provisioned@integration.test"
|
||||
provisioned_name = "zeus provisioned user"
|
||||
provisioned_password = "password123Z$"
|
||||
|
||||
# Step 1: Invite user as ADMIN (mirrors zeus inviteUserOnSigNoz)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/roles"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
role_id = next(role["id"] for role in response.json()["data"] if role["name"] == "signoz-admin")
|
||||
|
||||
create_payload = {
|
||||
"email": provisioned_email,
|
||||
"displayName": provisioned_name,
|
||||
"userRoles": [{"id": role_id}],
|
||||
}
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={
|
||||
"email": provisioned_email,
|
||||
"name": provisioned_name,
|
||||
"role": "ADMIN",
|
||||
},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json=create_payload,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
user_id = response.json()["data"]["id"]
|
||||
|
||||
# Step 2: List users to find the invited user's ID (mirrors zeus GET /api/v1/user)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/user"),
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json=create_payload,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
users = response.json()["data"]
|
||||
found_user = next((u for u in users if u["email"] == provisioned_email), None)
|
||||
assert found_user is not None
|
||||
user_id = found_user["id"]
|
||||
assert response.status_code == HTTPStatus.CONFLICT, response.text
|
||||
|
||||
# Step 3: Get reset password token (mirrors zeus GET /api/v1/getResetPasswordToken/{id})
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/getResetPasswordToken/{user_id}"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
existing_id = next(user["id"] for user in response.json()["data"] if user["email"] == provisioned_email.strip().lower())
|
||||
assert existing_id == user_id
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/reset_password_tokens"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
reset_token = response.json()["data"]["token"]
|
||||
assert reset_token is not None
|
||||
assert reset_token != ""
|
||||
|
||||
# Step 4: Use the token to set password and activate user
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": provisioned_password, "token": reset_token},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
# Step 5: Verify the provisioned user can log in and is active with admin role
|
||||
user_token = get_token(provisioned_email, provisioned_password)
|
||||
assert user_token is not None
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions/context"),
|
||||
params={"email": provisioned_email, "ref": f"{signoz.self.host_configs['8080'].base()}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
org_id = response.json()["data"]["orgs"][0]["id"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions/email_password"),
|
||||
json={"email": provisioned_email, "password": provisioned_password, "orgId": org_id},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
assert response.json()["data"]["accessToken"] != ""
|
||||
|
||||
provisioned_user = find_user_with_roles_by_email(signoz, admin_token, provisioned_email)
|
||||
assert provisioned_user["status"] == "active"
|
||||
|
||||
@@ -5,7 +5,7 @@ import requests
|
||||
from sqlalchemy import sql
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, find_user_by_email
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, create_active_user, find_user_by_email
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
@@ -17,24 +17,13 @@ PASSWORD_USER_PASSWORD = "password123Z$"
|
||||
def test_change_password(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create another admin user
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": PASSWORD_USER_EMAIL, "role": "ADMIN"},
|
||||
timeout=2,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=PASSWORD_USER_EMAIL,
|
||||
role="signoz-admin",
|
||||
password=PASSWORD_USER_PASSWORD,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
invited_user = response.json()["data"]
|
||||
reset_token = invited_user["token"]
|
||||
|
||||
# Reset password to activate user
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": PASSWORD_USER_PASSWORD, "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# Try logging in with the password
|
||||
token = get_token(PASSWORD_USER_EMAIL, PASSWORD_USER_PASSWORD)
|
||||
@@ -110,7 +99,7 @@ def test_reset_password(signoz: types.SigNoz, get_token: Callable[[str, str], st
|
||||
|
||||
# Reset the password with a bad password which should fail
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "password", "token": token},
|
||||
timeout=2,
|
||||
)
|
||||
@@ -119,51 +108,19 @@ def test_reset_password(signoz: types.SigNoz, get_token: Callable[[str, str], st
|
||||
|
||||
# Reset the password with a good password
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "password123Z$NEWNEW#!", "token": token},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
token = get_token(PASSWORD_USER_EMAIL, "password123Z$NEWNEW#!")
|
||||
assert token is not None
|
||||
|
||||
|
||||
def test_reset_password_v2(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
found_user = find_user_by_email(signoz, admin_token, PASSWORD_USER_EMAIL)
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{found_user['id']}/reset_password_tokens"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
token = response.json()["data"]["token"]
|
||||
|
||||
# A password failing the strength policy is rejected without consuming the token
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "password", "token": token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST, response.text
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "resetV2Password123Z$", "token": token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
assert get_token(PASSWORD_USER_EMAIL, "resetV2Password123Z$") is not None
|
||||
assert get_token(PASSWORD_USER_EMAIL, "password123Z$NEWNEW#!") is not None
|
||||
|
||||
# The token is single use, so replaying it no longer resolves
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "resetV2Password456Z$", "token": token},
|
||||
json={"password": "password123Z$REPLAY#!", "token": token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NOT_FOUND, response.text
|
||||
@@ -204,7 +161,7 @@ def test_reset_password_with_no_password(signoz: types.SigNoz, get_token: Callab
|
||||
|
||||
# Reset the password with a good password
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "FINALPASSword123!#[", "token": token},
|
||||
timeout=2,
|
||||
)
|
||||
@@ -262,28 +219,14 @@ def test_forgot_password_creates_reset_token(signoz: types.SigNoz, get_token: Ca
|
||||
forgot_email = "forgot@integration.test"
|
||||
|
||||
# Create a user specifically for testing forgot password
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={
|
||||
"email": forgot_email,
|
||||
"role": "EDITOR",
|
||||
"name": "forgotpassword user",
|
||||
},
|
||||
timeout=2,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=forgot_email,
|
||||
role="signoz-editor",
|
||||
password="originalPassword123Z$",
|
||||
name="forgotpassword user",
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
invited_user = response.json()["data"]
|
||||
reset_token = invited_user["token"]
|
||||
|
||||
# Activate user via reset password
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": "originalPassword123Z$", "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# Get org ID
|
||||
response = requests.get(
|
||||
@@ -326,7 +269,7 @@ def test_forgot_password_creates_reset_token(signoz: types.SigNoz, get_token: Ca
|
||||
|
||||
# Reset password with a valid strong password
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "newSecurePassword123Z$!", "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
@@ -416,7 +359,7 @@ def test_reset_password_with_expired_token(signoz: types.SigNoz, get_token: Call
|
||||
|
||||
# Try to use the expired token - should fail with 401 Unauthorized
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "expiredTokenPassword123Z$!", "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ from fixtures.auth import (
|
||||
change_user_role,
|
||||
create_active_user,
|
||||
)
|
||||
from fixtures.role import find_role_by_name
|
||||
|
||||
ROLECHANGE_USER_EMAIL = "admin+rolechange@integration.test"
|
||||
ROLECHANGE_USER_PASSWORD = "password123Z$"
|
||||
@@ -23,27 +24,14 @@ def test_change_role(
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a new user as VIEWER
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": ROLECHANGE_USER_EMAIL, "role": "VIEWER"},
|
||||
timeout=2,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=ROLECHANGE_USER_EMAIL,
|
||||
role="signoz-viewer",
|
||||
password=ROLECHANGE_USER_PASSWORD,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
invited_user = response.json()["data"]
|
||||
reset_token = invited_user["token"]
|
||||
|
||||
# Activate user via reset password
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": ROLECHANGE_USER_PASSWORD, "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# Make some API calls as new user
|
||||
new_user_token = get_token(ROLECHANGE_USER_EMAIL, ROLECHANGE_USER_PASSWORD)
|
||||
|
||||
@@ -133,7 +121,7 @@ def test_assign_role_is_additive(
|
||||
signoz: types.SigNoz,
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
"""Verify POST /api/v2/users/{id}/roles ADDS a role alongside existing ones and is idempotent."""
|
||||
"""Verify POST /api/v2/user_roles ADDS a role alongside existing ones and is idempotent."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
|
||||
@@ -144,15 +132,17 @@ def test_assign_role_is_additive(
|
||||
me = response.json()["data"]
|
||||
user_id = me["id"]
|
||||
|
||||
editor_role_id = find_role_by_name(signoz, admin_token, "signoz-editor")
|
||||
|
||||
# User currently has signoz-admin from test_change_role.
|
||||
# Assign signoz-editor — should be additive, admin stays.
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"),
|
||||
json={"name": "signoz-editor"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/user_roles"),
|
||||
json={"userId": user_id, "roleId": editor_role_id},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.status_code == HTTPStatus.CREATED
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"),
|
||||
@@ -168,12 +158,12 @@ def test_assign_role_is_additive(
|
||||
|
||||
# Idempotency: assigning the same role again succeeds without duplicates
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"),
|
||||
json={"name": "signoz-editor"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/user_roles"),
|
||||
json={"userId": user_id, "roleId": editor_role_id},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.status_code == HTTPStatus.CREATED
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"),
|
||||
@@ -225,7 +215,7 @@ def test_remove_role(
|
||||
signoz: types.SigNoz,
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
"""Verify DELETE /api/v2/users/{id}/roles/{roleId} removes only the specified role."""
|
||||
"""Verify DELETE /api/v2/user_roles/{id} removes only the specified role."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
|
||||
@@ -236,18 +226,11 @@ def test_remove_role(
|
||||
me = response.json()["data"]
|
||||
user_id = me["id"]
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
roles = response.json()["data"]
|
||||
editor_role_id = next((r for r in roles if r["name"] == "signoz-editor"), None)["id"]
|
||||
assert editor_role_id is not None
|
||||
editor_entry_id = next((ur["id"] for ur in me["userRoles"] if ur["role"]["name"] == "signoz-editor"), None)
|
||||
assert editor_entry_id is not None
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles/{editor_role_id}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/user_roles/{editor_entry_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
@@ -296,7 +279,7 @@ def test_admin_cannot_assign_role_to_self(
|
||||
signoz: types.SigNoz,
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
"""Verify POST /api/v2/users/{own_id}/roles is rejected (self-mutation guard)."""
|
||||
"""Verify POST /api/v2/user_roles for the caller's own user is rejected (self-mutation guard)."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
|
||||
@@ -307,8 +290,8 @@ def test_admin_cannot_assign_role_to_self(
|
||||
admin_data = response.json()["data"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{admin_data['id']}/roles"),
|
||||
json={"name": "signoz-editor"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/user_roles"),
|
||||
json={"userId": admin_data["id"], "roleId": find_role_by_name(signoz, admin_token, "signoz-editor")},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
@@ -319,7 +302,7 @@ def test_admin_cannot_remove_own_role(
|
||||
signoz: types.SigNoz,
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
"""Verify DELETE /api/v2/users/{own_id}/roles/{roleId} is rejected (self-mutation guard)."""
|
||||
"""Verify DELETE /api/v2/user_roles/{id} for the caller's own assignment is rejected (self-mutation guard)."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users/me"),
|
||||
@@ -329,18 +312,11 @@ def test_admin_cannot_remove_own_role(
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
admin_data = response.json()["data"]
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{admin_data['id']}/roles"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
roles = response.json()["data"]
|
||||
admin_role_id = next((r for r in roles if r["name"] == "signoz-admin"), None)["id"]
|
||||
assert admin_role_id is not None
|
||||
admin_entry_id = next((ur["id"] for ur in admin_data["userRoles"] if ur["role"]["name"] == "signoz-admin"), None)
|
||||
assert admin_entry_id is not None
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{admin_data['id']}/roles/{admin_role_id}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/user_roles/{admin_entry_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
@@ -359,7 +335,7 @@ def test_editor_cannot_manage_roles(
|
||||
signoz,
|
||||
admin_token,
|
||||
email="viewer+roleauth@integration.test",
|
||||
role="VIEWER",
|
||||
role="signoz-viewer",
|
||||
password=ROLECHANGE_USER_PASSWORD,
|
||||
name="viewer roleauth",
|
||||
)
|
||||
@@ -376,8 +352,8 @@ def test_editor_cannot_manage_roles(
|
||||
|
||||
# POST assign role — forbidden
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{viewer_id}/roles"),
|
||||
json={"name": "signoz-editor"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/user_roles"),
|
||||
json={"userId": viewer_id, "roleId": find_role_by_name(signoz, admin_token, "signoz-editor")},
|
||||
headers={"Authorization": f"Bearer {editor_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
@@ -385,16 +361,15 @@ def test_editor_cannot_manage_roles(
|
||||
|
||||
# DELETE remove role — forbidden
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{viewer_id}/roles"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{viewer_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
viewer_roles = response.json()["data"]
|
||||
viewer_role_id = next((r for r in viewer_roles if r["name"] == "signoz-viewer"), None)["id"]
|
||||
viewer_entry_id = next(ur["id"] for ur in response.json()["data"]["userRoles"] if ur["role"]["name"] == "signoz-viewer")
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{viewer_id}/roles/{viewer_role_id}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/user_roles/{viewer_entry_id}"),
|
||||
headers={"Authorization": f"Bearer {editor_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ from http import HTTPStatus
|
||||
import requests
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.role import find_role_by_name
|
||||
from fixtures.types import SigNoz
|
||||
|
||||
DUPLICATE_USER_EMAIL = "duplicate@integration.test"
|
||||
@@ -20,38 +21,49 @@ def test_duplicate_user_invite_rejected(
|
||||
"""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
viewer_role_id = find_role_by_name(signoz, admin_token, "signoz-viewer")
|
||||
|
||||
# Invite a new user
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": DUPLICATE_USER_EMAIL, "role": "EDITOR"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={
|
||||
"email": DUPLICATE_USER_EMAIL,
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, "signoz-editor")}],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
invited_user = response.json()["data"]
|
||||
reset_token = invited_user["token"]
|
||||
user_id = response.json()["data"]["id"]
|
||||
|
||||
# Invite the same email again — should fail
|
||||
# Invite the same email again while still pending — should fail
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": DUPLICATE_USER_EMAIL, "role": "VIEWER"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={"email": DUPLICATE_USER_EMAIL, "userRoles": [{"id": viewer_role_id}]},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CONFLICT
|
||||
|
||||
# activate the user
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/reset_password_tokens"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": "password123Z$", "token": reset_token},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/factor_password/reset"),
|
||||
json={"password": "password123Z$", "token": response.json()["data"]["token"]},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# Try to invite the same email again — should fail
|
||||
# Try to invite the same email again once active — should fail
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": DUPLICATE_USER_EMAIL, "role": "VIEWER"},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={"email": DUPLICATE_USER_EMAIL, "userRoles": [{"id": viewer_role_id}]},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,12 @@ from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, create_active_user
|
||||
from fixtures.auth import (
|
||||
USER_ADMIN_EMAIL,
|
||||
USER_ADMIN_PASSWORD,
|
||||
assert_user_has_role,
|
||||
create_active_user,
|
||||
)
|
||||
from fixtures.types import SigNoz
|
||||
|
||||
|
||||
@@ -22,71 +27,45 @@ def test_reinvite_deleted_user(
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
reinvite_user_email = "reinvite@integration.test"
|
||||
reinvite_user_name = "reinvite user"
|
||||
reinvite_user_role = "EDITOR"
|
||||
reinvite_user_password = "password123Z$"
|
||||
|
||||
# invite the user
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={
|
||||
"email": reinvite_user_email,
|
||||
"role": reinvite_user_role,
|
||||
"name": reinvite_user_name,
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
user_id = create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=reinvite_user_email,
|
||||
role="signoz-editor",
|
||||
password="password123Z$",
|
||||
name="reinvite user",
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
invited_user = response.json()["data"]
|
||||
reset_token = invited_user["token"]
|
||||
|
||||
# reset the password to make it active
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": reinvite_user_password, "token": reset_token},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# call the delete api which now soft deletes the user
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{invited_user['id']}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# Re-invite the same email — should succeed
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={
|
||||
"email": reinvite_user_email,
|
||||
"role": "VIEWER",
|
||||
"name": "reinvite user v2",
|
||||
},
|
||||
# Re-invite the same email — should succeed and create a different user
|
||||
reinvited_user_id = create_active_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=reinvite_user_email,
|
||||
role="signoz-viewer",
|
||||
password="newPassword123Z$",
|
||||
name="reinvite user v2",
|
||||
)
|
||||
assert reinvited_user_id != user_id
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{reinvited_user_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
reinvited_user = response.json()["data"]
|
||||
assert reinvited_user["role"] == "VIEWER"
|
||||
assert reinvited_user["id"] != invited_user["id"] # confirms a new user was created
|
||||
|
||||
reinvited_user_reset_password_token = reinvited_user["token"]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={
|
||||
"password": "newPassword123Z$",
|
||||
"token": reinvited_user_reset_password_token,
|
||||
},
|
||||
timeout=2,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
assert_user_has_role(response.json()["data"], "signoz-viewer")
|
||||
|
||||
# Verify user can log in with new password
|
||||
user_token = get_token("reinvite@integration.test", "newPassword123Z$")
|
||||
user_token = get_token(reinvite_user_email, "newPassword123Z$")
|
||||
assert user_token is not None
|
||||
|
||||
|
||||
@@ -105,7 +84,7 @@ def test_delete_user(
|
||||
signoz,
|
||||
admin_token,
|
||||
email="delete-verify-v2@integration.test",
|
||||
role="EDITOR",
|
||||
role="signoz-editor",
|
||||
password="password123Z$",
|
||||
name="delete verify v2",
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from sqlalchemy import sql
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.role import find_role_by_name
|
||||
from fixtures.types import SigNoz
|
||||
|
||||
UNIQUE_INDEX_USER_EMAIL = "useruniqueindex@integration.test"
|
||||
@@ -41,11 +42,11 @@ def test_unique_index_allows_multiple_deleted_rows(
|
||||
|
||||
# Step 1: invite and delete the first user
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={
|
||||
"email": UNIQUE_INDEX_USER_EMAIL,
|
||||
"role": "EDITOR",
|
||||
"name": "unique index user v1",
|
||||
"displayName": "unique index user v1",
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, "signoz-editor")}],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
@@ -62,11 +63,11 @@ def test_unique_index_allows_multiple_deleted_rows(
|
||||
|
||||
# Step 2: re-invite and delete the same email (second deleted row)
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
json={
|
||||
"email": UNIQUE_INDEX_USER_EMAIL,
|
||||
"role": "EDITOR",
|
||||
"name": "unique index user v2",
|
||||
"displayName": "unique index user v2",
|
||||
"userRoles": [{"id": find_role_by_name(signoz, admin_token, "signoz-editor")}],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_setup(
|
||||
transaction_group("read", "telemetryresource", "traces", ["builder_query/signoz.workspace.key.id/key-a"]),
|
||||
],
|
||||
)
|
||||
user_id = create_active_user(signoz, admin_token, email=scoped_email, role="VIEWER", password=user_password)
|
||||
user_id = create_active_user(signoz, admin_token, email=scoped_email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", scoped_role)
|
||||
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ def test_setup(
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
create_role(admin_token, any_key_role, [transaction_group("read", "telemetryresource", "logs", ["builder_query/signoz.workspace.key.id/*"])])
|
||||
any_user = create_active_user(signoz, admin_token, email=any_key_email, role="VIEWER", password=user_password)
|
||||
any_user = create_active_user(signoz, admin_token, email=any_key_email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, any_user, "signoz-viewer", any_key_role)
|
||||
|
||||
create_role(admin_token, builder_all_role, [transaction_group("read", "telemetryresource", "logs", ["builder_query/*"])])
|
||||
all_user = create_active_user(signoz, admin_token, email=builder_all_email, role="VIEWER", password=user_password)
|
||||
all_user = create_active_user(signoz, admin_token, email=builder_all_email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, all_user, "signoz-viewer", builder_all_role)
|
||||
|
||||
|
||||
|
||||
@@ -38,15 +38,15 @@ def test_setup(
|
||||
transaction_group("read", "telemetryresource", "meter-metrics", ["clickhouse_sql/*"]),
|
||||
],
|
||||
)
|
||||
chsql_user = create_active_user(signoz, admin_token, email=chsql_email, role="VIEWER", password=user_password)
|
||||
chsql_user = create_active_user(signoz, admin_token, email=chsql_email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, chsql_user, "signoz-viewer", chsql_role)
|
||||
|
||||
create_role(admin_token, key_a_role, [transaction_group("read", "telemetryresource", "traces", ["builder_query/signoz.workspace.key.id/key-a"])])
|
||||
key_a_user = create_active_user(signoz, admin_token, email=key_a_email, role="VIEWER", password=user_password)
|
||||
key_a_user = create_active_user(signoz, admin_token, email=key_a_email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, key_a_user, "signoz-viewer", key_a_role)
|
||||
|
||||
# A plain managed viewer (signoz-viewer) — for the meter-metrics/audit-logs policy checks.
|
||||
create_active_user(signoz, admin_token, email=viewer_email, role="VIEWER", password=user_password)
|
||||
create_active_user(signoz, admin_token, email=viewer_email, role="signoz-viewer", password=user_password)
|
||||
|
||||
|
||||
def test_clickhouse_sql_requires_chsql_grant(
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_setup(
|
||||
) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
create_role(admin_token, spacey_role, [transaction_group("read", "telemetryresource", "logs", ["builder_query/signoz.workspace.key.id/key with space"])])
|
||||
user_id = create_active_user(signoz, admin_token, email=spacey_email, role="VIEWER", password=user_password)
|
||||
user_id = create_active_user(signoz, admin_token, email=spacey_email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", spacey_role)
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_setup(
|
||||
) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
create_role(admin_token, keywild_role, [transaction_group("read", "telemetryresource", "logs", ["builder_query/signoz.workspace.key.id/*"])])
|
||||
user_id = create_active_user(signoz, admin_token, email=keywild_email, role="VIEWER", password=user_password)
|
||||
user_id = create_active_user(signoz, admin_token, email=keywild_email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", keywild_role)
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ from collections.abc import Callable
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.logs import Logs
|
||||
from fixtures.querier import build_order_by, build_raw_query, get_rows, make_query_request
|
||||
|
||||
|
||||
def test_logs_json_body_simple_searches(
|
||||
@@ -911,3 +913,61 @@ def test_logs_json_body_listing(
|
||||
assert len(results) == 1
|
||||
count = results[0]["data"][0][0]
|
||||
assert count == 4 # 4 logs have status="success"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"expression,expected_services",
|
||||
[
|
||||
pytest.param("body.service IN ['auth', 'payment']", {"auth", "payment"}, id="in_scalar_path"),
|
||||
pytest.param("body.status IN [200, 500]", {"auth", "payment"}, id="in_number_path"),
|
||||
pytest.param("body.service NOT IN ['auth']", {"payment", "search"}, id="not_in_scalar_path"),
|
||||
# An `[]` path is extracted as an array. Comparing that array to each scalar in the
|
||||
# list is something ClickHouse rejects outright (code 130), so this shape used to
|
||||
# fail the whole query; per-value extraction reads the first element instead.
|
||||
pytest.param("body.user_names[*] IN ['alpha', 'gamma']", {"auth", "payment"}, id="in_array_path"),
|
||||
],
|
||||
)
|
||||
def test_logs_json_body_in_operator(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_logs: Callable[[list[Logs]], None],
|
||||
expression: str,
|
||||
expected_services: set[str],
|
||||
) -> None:
|
||||
"""IN over a body JSON path fans out to one comparison per value."""
|
||||
now = datetime.now(tz=UTC).replace(second=0, microsecond=0)
|
||||
specs = [("auth", 200, ["alpha", "beta"]), ("payment", 500, ["gamma"]), ("search", 404, ["beta", "alpha"])]
|
||||
insert_logs(
|
||||
[
|
||||
Logs(
|
||||
timestamp=now - timedelta(seconds=i + 1),
|
||||
resources={"service.name": "api"},
|
||||
body=json.dumps({"service": service, "status": status, "user_names": user_names}),
|
||||
)
|
||||
for i, (service, status, user_names) in enumerate(specs)
|
||||
]
|
||||
)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
response = make_query_request(
|
||||
signoz,
|
||||
token,
|
||||
start_ms=int((now - timedelta(minutes=5)).timestamp() * 1000),
|
||||
end_ms=int(now.timestamp() * 1000),
|
||||
request_type="raw",
|
||||
queries=[
|
||||
build_raw_query(
|
||||
"A",
|
||||
"logs",
|
||||
filter_expression=expression,
|
||||
order=[build_order_by("timestamp", "desc")],
|
||||
limit=100,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
assert response.json()["status"] == "success"
|
||||
# flag off: the body comes back as the raw JSON string
|
||||
assert {json.loads(row["data"]["body"])["service"] for row in get_rows(response)} == expected_services
|
||||
|
||||
@@ -9,6 +9,7 @@ from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
|
||||
from fixtures.role import (
|
||||
expected_managed_transaction_keys,
|
||||
find_role_by_name,
|
||||
flatten_transaction_groups,
|
||||
managed_role_names,
|
||||
)
|
||||
@@ -67,11 +68,10 @@ def test_managed_role_transactions_match_expected(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
role_name: str,
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, role_name)
|
||||
role_id = find_role_by_name(signoz, admin_token, role_name)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{role_id}"),
|
||||
|
||||
@@ -12,7 +12,7 @@ from fixtures.auth import (
|
||||
create_active_user,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.role import flatten_transaction_groups, transaction_group
|
||||
from fixtures.role import find_role_by_name, flatten_transaction_groups, transaction_group
|
||||
|
||||
CRUD_ROLE_NAME = "crud-test-role"
|
||||
CRUD_ASSIGNEE_ROLE_NAME = "crud-assignee-role"
|
||||
@@ -61,10 +61,9 @@ def test_declarative_update_adds_and_removes_transactions(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, CRUD_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, CRUD_ROLE_NAME)
|
||||
|
||||
def put_transactions(groups: list[dict]) -> None:
|
||||
resp = requests.put(
|
||||
@@ -208,10 +207,9 @@ def test_managed_role_is_immutable(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
admin_role_id = find_role_id(admin_token, "signoz-admin")
|
||||
admin_role_id = find_role_by_name(signoz, admin_token, "signoz-admin")
|
||||
|
||||
resp = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{admin_role_id}"),
|
||||
@@ -239,27 +237,27 @@ def test_delete_role_with_assignee_guarded(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=CRUD_ASSIGNEE_USER_EMAIL,
|
||||
role="VIEWER",
|
||||
role="signoz-viewer",
|
||||
password=CRUD_ASSIGNEE_USER_PASSWORD,
|
||||
name="crud-assignee-user",
|
||||
)
|
||||
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"),
|
||||
json={"name": CRUD_ASSIGNEE_ROLE_NAME},
|
||||
signoz.self.host_configs["8080"].get("/api/v2/user_roles"),
|
||||
json={"userId": user_id, "roleId": role_id},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
assert resp.status_code == HTTPStatus.CREATED, resp.text
|
||||
|
||||
resp = requests.delete(signoz.self.host_configs["8080"].get(f"/api/v1/roles/{role_id}"), headers={"Authorization": f"Bearer {admin_token}"}, timeout=5)
|
||||
assert resp.status_code == HTTPStatus.BAD_REQUEST, f"delete role with assignee: expected 400, got {resp.status_code}: {resp.text}"
|
||||
|
||||
resp = requests.get(signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles"), headers={"Authorization": f"Bearer {admin_token}"}, timeout=5)
|
||||
resp = requests.get(signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}"), headers={"Authorization": f"Bearer {admin_token}"}, timeout=5)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
entry = next(r for r in resp.json()["data"] if r["name"] == CRUD_ASSIGNEE_ROLE_NAME)
|
||||
entry = next(ur for ur in resp.json()["data"]["userRoles"] if ur["role"]["name"] == CRUD_ASSIGNEE_ROLE_NAME)
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{user_id}/roles/{entry['id']}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/user_roles/{entry['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ from fixtures.auth import (
|
||||
create_active_user,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.role import transaction_group
|
||||
from fixtures.role import find_role_by_name, transaction_group
|
||||
|
||||
_ACTOR_ROLE_NAME = "role-fga-actor"
|
||||
_ACTOR_USER_EMAIL = "customrole+rolefga@integration.test"
|
||||
@@ -57,7 +57,7 @@ def test_setup_actor_and_targets(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=_ACTOR_USER_EMAIL,
|
||||
role="VIEWER",
|
||||
role="signoz-viewer",
|
||||
password=_ACTOR_USER_PASSWORD,
|
||||
name="role-fga-test-user",
|
||||
)
|
||||
@@ -68,12 +68,11 @@ def test_read_scoped_to_granted_role(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(_ACTOR_USER_EMAIL, _ACTOR_USER_PASSWORD)
|
||||
a_id = find_role_id(admin_token, _TARGET_A)
|
||||
b_id = find_role_id(admin_token, _TARGET_B)
|
||||
a_id = find_role_by_name(signoz, admin_token, _TARGET_A)
|
||||
b_id = find_role_by_name(signoz, admin_token, _TARGET_B)
|
||||
|
||||
resp = requests.get(signoz.self.host_configs["8080"].get(f"/api/v1/roles/{a_id}"), headers={"Authorization": f"Bearer {token}"}, timeout=5)
|
||||
assert resp.status_code == HTTPStatus.OK, f"read granted role: {resp.text}"
|
||||
@@ -101,11 +100,10 @@ def test_create_is_collection_scoped(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
create_role: Callable[..., str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
actor_id = find_role_id(admin_token, _ACTOR_ROLE_NAME)
|
||||
actor_id = find_role_by_name(signoz, admin_token, _ACTOR_ROLE_NAME)
|
||||
token = get_token(_ACTOR_USER_EMAIL, _ACTOR_USER_PASSWORD)
|
||||
|
||||
resp = requests.post(
|
||||
@@ -138,12 +136,11 @@ def test_update_scoped_to_granted_role(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
actor_id = find_role_id(admin_token, _ACTOR_ROLE_NAME)
|
||||
a_id = find_role_id(admin_token, _TARGET_A)
|
||||
b_id = find_role_id(admin_token, _TARGET_B)
|
||||
actor_id = find_role_by_name(signoz, admin_token, _ACTOR_ROLE_NAME)
|
||||
a_id = find_role_by_name(signoz, admin_token, _TARGET_A)
|
||||
b_id = find_role_by_name(signoz, admin_token, _TARGET_B)
|
||||
|
||||
resp = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{actor_id}"),
|
||||
@@ -184,12 +181,11 @@ def test_delete_scoped_to_granted_role(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
actor_id = find_role_id(admin_token, _ACTOR_ROLE_NAME)
|
||||
a_id = find_role_id(admin_token, _TARGET_A)
|
||||
b_id = find_role_id(admin_token, _TARGET_B)
|
||||
actor_id = find_role_by_name(signoz, admin_token, _ACTOR_ROLE_NAME)
|
||||
a_id = find_role_by_name(signoz, admin_token, _TARGET_A)
|
||||
b_id = find_role_by_name(signoz, admin_token, _TARGET_B)
|
||||
|
||||
resp = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{actor_id}"),
|
||||
@@ -221,11 +217,10 @@ def test_revoke_read(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
actor_id = find_role_id(admin_token, _ACTOR_ROLE_NAME)
|
||||
b_id = find_role_id(admin_token, _TARGET_B)
|
||||
actor_id = find_role_by_name(signoz, admin_token, _ACTOR_ROLE_NAME)
|
||||
b_id = find_role_by_name(signoz, admin_token, _TARGET_B)
|
||||
token = get_token(_ACTOR_USER_EMAIL, _ACTOR_USER_PASSWORD)
|
||||
|
||||
resp = requests.put(
|
||||
@@ -261,7 +256,6 @@ def test_role_fga_cleanup(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
user = find_user_by_email(signoz, admin_token, _ACTOR_USER_EMAIL)
|
||||
@@ -279,7 +273,7 @@ def test_role_fga_cleanup(
|
||||
|
||||
for name in (_ACTOR_ROLE_NAME, _TARGET_B, _CREATED_ROLE):
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{find_role_id(admin_token, name)}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{find_role_by_name(signoz, admin_token, name)}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ from fixtures.auth import (
|
||||
create_active_user,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.role import transaction_group
|
||||
from fixtures.role import find_role_by_name, transaction_group
|
||||
from fixtures.savedview import SAVED_VIEW_BASE, create_saved_view, find_saved_view_by_name
|
||||
|
||||
_SAVED_VIEW_FGA_CUSTOM_ROLE_NAME = "saved-view-fga-readonly"
|
||||
@@ -57,7 +57,7 @@ def test_create_custom_role_readonly_view(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=_SAVED_VIEW_FGA_CUSTOM_USER_EMAIL,
|
||||
role="VIEWER",
|
||||
role="signoz-viewer",
|
||||
password=_SAVED_VIEW_FGA_CUSTOM_USER_PASSWORD,
|
||||
name="saved-view-fga-test-user",
|
||||
)
|
||||
@@ -152,10 +152,9 @@ def test_create_is_collection_scoped(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
target_id = find_saved_view_by_name(signoz, admin_token, _SAVED_VIEW_FGA_TARGET_NAME)["id"]
|
||||
|
||||
resp = requests.put(
|
||||
@@ -184,10 +183,9 @@ def test_update_scoped_to_granted_view(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
target_id = find_saved_view_by_name(signoz, admin_token, _SAVED_VIEW_FGA_TARGET_NAME)["id"]
|
||||
other_id = find_saved_view_by_name(signoz, admin_token, _SAVED_VIEW_FGA_OTHER_NAME)["id"]
|
||||
|
||||
@@ -242,10 +240,9 @@ def test_delete_scoped_to_granted_view(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
target_id = find_saved_view_by_name(signoz, admin_token, _SAVED_VIEW_FGA_TARGET_NAME)["id"]
|
||||
other_id = find_saved_view_by_name(signoz, admin_token, _SAVED_VIEW_FGA_OTHER_NAME)["id"]
|
||||
|
||||
@@ -279,10 +276,9 @@ def test_revoke_read_scoped(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)
|
||||
other_id = find_saved_view_by_name(signoz, admin_token, _SAVED_VIEW_FGA_OTHER_NAME)["id"]
|
||||
|
||||
resp = requests.put(
|
||||
@@ -319,7 +315,6 @@ def test_saved_view_fga_cleanup(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
user = find_user_by_email(signoz, admin_token, _SAVED_VIEW_FGA_CUSTOM_USER_EMAIL)
|
||||
@@ -336,7 +331,7 @@ def test_saved_view_fga_cleanup(
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, f"remove role from user: {resp.text}"
|
||||
|
||||
resp = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{find_role_id(admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)}"),
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{find_role_by_name(signoz, admin_token, _SAVED_VIEW_FGA_CUSTOM_ROLE_NAME)}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ from fixtures.auth import (
|
||||
create_active_user,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.role import transaction_group
|
||||
from fixtures.role import find_role_by_name, transaction_group
|
||||
from fixtures.serviceaccount import (
|
||||
SERVICE_ACCOUNT_BASE,
|
||||
create_service_account,
|
||||
@@ -70,7 +70,7 @@ def test_create_custom_role_readonly_sa(
|
||||
signoz,
|
||||
admin_token,
|
||||
email=_SA_FGA_CUSTOM_USER_EMAIL,
|
||||
role="VIEWER",
|
||||
role="signoz-viewer",
|
||||
password=_SA_FGA_CUSTOM_USER_PASSWORD,
|
||||
name="sa-fga-test-user",
|
||||
)
|
||||
@@ -116,12 +116,11 @@ def test_write_forbidden_without_grant(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(_SA_FGA_CUSTOM_USER_EMAIL, _SA_FGA_CUSTOM_USER_PASSWORD)
|
||||
target_id = find_service_account_by_name(signoz, admin_token, _SA_FGA_TARGET_SA_NAME)["id"]
|
||||
viewer_role_id = find_role_id(admin_token, "signoz-viewer")
|
||||
viewer_role_id = find_role_by_name(signoz, admin_token, "signoz-viewer")
|
||||
|
||||
resp = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{SERVICE_ACCOUNT_BASE}/{target_id}"),
|
||||
@@ -144,10 +143,9 @@ def test_update_scoped_to_granted_sa(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
target_id = find_service_account_by_name(signoz, admin_token, _SA_FGA_TARGET_SA_NAME)["id"]
|
||||
other_id = find_service_account_by_name(signoz, admin_token, _SA_FGA_OTHER_SA_NAME)["id"]
|
||||
|
||||
@@ -191,14 +189,13 @@ def test_attach_detach_dual_scoped(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
target_id = find_service_account_by_name(signoz, admin_token, _SA_FGA_TARGET_SA_NAME)["id"]
|
||||
other_id = find_service_account_by_name(signoz, admin_token, _SA_FGA_OTHER_SA_NAME)["id"]
|
||||
editor_role_id = find_role_id(admin_token, "signoz-editor")
|
||||
viewer_role_id = find_role_id(admin_token, "signoz-viewer")
|
||||
editor_role_id = find_role_by_name(signoz, admin_token, "signoz-editor")
|
||||
viewer_role_id = find_role_by_name(signoz, admin_token, "signoz-viewer")
|
||||
|
||||
# attach/detach granted on the target SA id AND the signoz-editor role name only.
|
||||
resp = requests.put(
|
||||
@@ -261,10 +258,9 @@ def test_revoke_read_scoped(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
target_id = find_service_account_by_name(signoz, admin_token, _SA_FGA_TARGET_SA_NAME)["id"]
|
||||
|
||||
resp = requests.put(
|
||||
@@ -284,10 +280,9 @@ def test_delete_custom_role_cleanup(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
find_role_id: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_id(admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
role_id = find_role_by_name(signoz, admin_token, _SA_FGA_CUSTOM_ROLE_NAME)
|
||||
user = find_user_by_email(signoz, admin_token, _SA_FGA_CUSTOM_USER_EMAIL)
|
||||
|
||||
resp = requests.get(signoz.self.host_configs["8080"].get(f"/api/v2/users/{user['id']}/roles"), headers={"Authorization": f"Bearer {admin_token}"}, timeout=5)
|
||||
|
||||
Reference in New Issue
Block a user