mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-26 02:12:34 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8b28edaf2 | ||
|
|
fdbbbaa43b |
@@ -82,12 +82,6 @@ exporters:
|
||||
timeout: 45s
|
||||
sending_queue:
|
||||
enabled: false
|
||||
metadataexporter:
|
||||
cache:
|
||||
provider: in_memory
|
||||
dsn: tcp://clickhouse:9000/signoz_metadata
|
||||
enabled: true
|
||||
timeout: 45s
|
||||
service:
|
||||
telemetry:
|
||||
logs:
|
||||
@@ -99,19 +93,19 @@ service:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [signozspanmetrics/delta, batch]
|
||||
exporters: [clickhousetraces, metadataexporter, signozmeter]
|
||||
exporters: [clickhousetraces, signozmeter]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [signozclickhousemetrics, metadataexporter, signozmeter]
|
||||
exporters: [signozclickhousemetrics, signozmeter]
|
||||
metrics/prometheus:
|
||||
receivers: [prometheus]
|
||||
processors: [batch]
|
||||
exporters: [signozclickhousemetrics, metadataexporter, signozmeter]
|
||||
exporters: [signozclickhousemetrics, signozmeter]
|
||||
logs:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [clickhouselogsexporter, metadataexporter, signozmeter]
|
||||
exporters: [clickhouselogsexporter, signozmeter]
|
||||
metrics/meter:
|
||||
receivers: [signozmeter]
|
||||
processors: [batch/meter]
|
||||
|
||||
@@ -82,12 +82,6 @@ exporters:
|
||||
timeout: 45s
|
||||
sending_queue:
|
||||
enabled: false
|
||||
metadataexporter:
|
||||
cache:
|
||||
provider: in_memory
|
||||
dsn: tcp://clickhouse:9000/signoz_metadata
|
||||
enabled: true
|
||||
timeout: 45s
|
||||
service:
|
||||
telemetry:
|
||||
logs:
|
||||
@@ -99,19 +93,19 @@ service:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [signozspanmetrics/delta, batch]
|
||||
exporters: [clickhousetraces, metadataexporter, signozmeter]
|
||||
exporters: [clickhousetraces, signozmeter]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [signozclickhousemetrics, metadataexporter, signozmeter]
|
||||
exporters: [signozclickhousemetrics, signozmeter]
|
||||
metrics/prometheus:
|
||||
receivers: [prometheus]
|
||||
processors: [batch]
|
||||
exporters: [signozclickhousemetrics, metadataexporter, signozmeter]
|
||||
exporters: [signozclickhousemetrics, signozmeter]
|
||||
logs:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [clickhouselogsexporter, metadataexporter, signozmeter]
|
||||
exporters: [clickhouselogsexporter, signozmeter]
|
||||
metrics/meter:
|
||||
receivers: [signozmeter]
|
||||
processors: [batch/meter]
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils/times"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils/timestamp"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/formatter"
|
||||
"github.com/SigNoz/signoz/pkg/formatter"
|
||||
|
||||
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
|
||||
|
||||
export const defaultTableStyle: CSSProperties = {
|
||||
minWidth: '40rem',
|
||||
maxWidth: '90rem',
|
||||
maxWidth: '60rem',
|
||||
};
|
||||
|
||||
export const defaultListViewPanelStyle: CSSProperties = {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import type { InputRef } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Collapse,
|
||||
@@ -47,23 +46,12 @@ function Overview({
|
||||
handleChangeSelectedView,
|
||||
}: Props): JSX.Element {
|
||||
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
||||
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(true);
|
||||
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
|
||||
const [isAttributesExpanded, setIsAttributesExpanded] = useState<boolean>(
|
||||
true,
|
||||
);
|
||||
const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
|
||||
|
||||
const focusTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const searchInputRef = useCallback((node: InputRef | null) => {
|
||||
clearTimeout(focusTimerRef.current);
|
||||
if (node) {
|
||||
focusTimerRef.current = setTimeout(() => node.focus(), 100);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => (): void => clearTimeout(focusTimerRef.current), []);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const options: EditorProps['options'] = {
|
||||
@@ -208,7 +196,7 @@ function Overview({
|
||||
<>
|
||||
{isSearchVisible && (
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
autoFocus
|
||||
placeholder="Search for a field..."
|
||||
className="search-input"
|
||||
value={fieldSearchInput}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
|
||||
import { getColorsForSeverityLabels, isRedLike } from '../utils';
|
||||
|
||||
describe('getColorsForSeverityLabels', () => {
|
||||
it('should return slate for blank labels', () => {
|
||||
expect(getColorsForSeverityLabels('', 0)).toBe(Color.BG_SLATE_300);
|
||||
expect(getColorsForSeverityLabels(' ', 0)).toBe(Color.BG_SLATE_300);
|
||||
});
|
||||
|
||||
it('should return correct colors for known severity variants', () => {
|
||||
expect(getColorsForSeverityLabels('INFO', 0)).toBe(Color.BG_ROBIN_600);
|
||||
expect(getColorsForSeverityLabels('ERROR', 0)).toBe(Color.BG_CHERRY_600);
|
||||
expect(getColorsForSeverityLabels('WARN', 0)).toBe(Color.BG_AMBER_600);
|
||||
expect(getColorsForSeverityLabels('DEBUG', 0)).toBe(Color.BG_AQUA_600);
|
||||
expect(getColorsForSeverityLabels('TRACE', 0)).toBe(Color.BG_FOREST_600);
|
||||
expect(getColorsForSeverityLabels('FATAL', 0)).toBe(Color.BG_SAKURA_600);
|
||||
});
|
||||
|
||||
it('should return non-red colors for unrecognized labels at any index', () => {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const color = getColorsForSeverityLabels('4', i);
|
||||
expect(isRedLike(color)).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return non-red colors for numeric severity text', () => {
|
||||
const numericLabels = ['1', '2', '4', '9', '13', '17', '21'];
|
||||
numericLabels.forEach((label) => {
|
||||
const color = getColorsForSeverityLabels(label, 0);
|
||||
expect(isRedLike(color)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,7 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
|
||||
// Function to determine if a color is "red-like" based on its RGB values
|
||||
export function isRedLike(hex: string): boolean {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return r > 180 && r > g * 1.4 && r > b * 1.4;
|
||||
}
|
||||
|
||||
const SAFE_FALLBACK_COLORS = colors.filter((c) => !isRedLike(c));
|
||||
|
||||
const SEVERITY_VARIANT_COLORS: Record<string, string> = {
|
||||
TRACE: Color.BG_FOREST_600,
|
||||
Trace: Color.BG_FOREST_500,
|
||||
@@ -76,13 +67,8 @@ export function getColorsForSeverityLabels(
|
||||
label: string,
|
||||
index: number,
|
||||
): string {
|
||||
const trimmed = label.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return Color.BG_SLATE_300;
|
||||
}
|
||||
|
||||
const variantColor = SEVERITY_VARIANT_COLORS[trimmed];
|
||||
// Check if we have a direct mapping for this severity variant
|
||||
const variantColor = SEVERITY_VARIANT_COLORS[label.trim()];
|
||||
if (variantColor) {
|
||||
return variantColor;
|
||||
}
|
||||
@@ -117,8 +103,5 @@ export function getColorsForSeverityLabels(
|
||||
return Color.BG_SAKURA_500;
|
||||
}
|
||||
|
||||
return (
|
||||
SAFE_FALLBACK_COLORS[index % SAFE_FALLBACK_COLORS.length] ||
|
||||
Color.BG_SLATE_400
|
||||
);
|
||||
return colors[index % colors.length] || themeColors.red;
|
||||
}
|
||||
|
||||
@@ -111,19 +111,23 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
||||
);
|
||||
|
||||
const itemContent = useCallback(
|
||||
(index: number, log: Record<string, unknown>): JSX.Element => (
|
||||
<TableRow
|
||||
tableColumns={tableColumns}
|
||||
index={index}
|
||||
log={log}
|
||||
logs={tableViewProps.logs}
|
||||
hasActions
|
||||
fontSize={tableViewProps.fontSize}
|
||||
onShowLogDetails={onSetActiveLog}
|
||||
isActiveLog={activeLog?.id === log.id}
|
||||
onClearActiveLog={onCloseActiveLog}
|
||||
/>
|
||||
),
|
||||
(index: number, log: Record<string, unknown>): JSX.Element => {
|
||||
return (
|
||||
<div key={log.id as string}>
|
||||
<TableRow
|
||||
tableColumns={tableColumns}
|
||||
index={index}
|
||||
log={log}
|
||||
logs={tableViewProps.logs}
|
||||
hasActions
|
||||
fontSize={tableViewProps.fontSize}
|
||||
onShowLogDetails={onSetActiveLog}
|
||||
isActiveLog={activeLog?.id === log.id}
|
||||
onClearActiveLog={onCloseActiveLog}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[
|
||||
tableColumns,
|
||||
onSetActiveLog,
|
||||
@@ -139,8 +143,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
||||
{tableColumns
|
||||
.filter((column) => column.key)
|
||||
.map((column) => {
|
||||
const isDragColumn =
|
||||
column.key !== 'expand' && column.key !== 'state-indicator';
|
||||
const isDragColumn = column.key !== 'expand';
|
||||
|
||||
return (
|
||||
<TableHeaderCellStyled
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useIsTextSelected } from 'hooks/useIsTextSelected';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import useLogDetailHandlers from '../useLogDetailHandlers';
|
||||
|
||||
jest.mock('hooks/logs/useActiveLog');
|
||||
jest.mock('hooks/useIsTextSelected');
|
||||
|
||||
const mockOnSetActiveLog = jest.fn();
|
||||
const mockOnClearActiveLog = jest.fn();
|
||||
const mockOnAddToQuery = jest.fn();
|
||||
const mockOnGroupByAttribute = jest.fn();
|
||||
const mockIsTextSelected = jest.fn();
|
||||
|
||||
const mockLog: ILog = {
|
||||
id: 'log-1',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
date: '2024-01-01',
|
||||
body: 'test log body',
|
||||
severityText: 'INFO',
|
||||
severityNumber: 9,
|
||||
traceFlags: 0,
|
||||
traceId: '',
|
||||
spanID: '',
|
||||
attributesString: {},
|
||||
attributesInt: {},
|
||||
attributesFloat: {},
|
||||
resources_string: {},
|
||||
scope_string: {},
|
||||
attributes_string: {},
|
||||
severity_text: '',
|
||||
severity_number: 0,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
jest.mocked(useIsTextSelected).mockReturnValue(mockIsTextSelected);
|
||||
|
||||
jest.mocked(useActiveLog).mockReturnValue({
|
||||
activeLog: null,
|
||||
onSetActiveLog: mockOnSetActiveLog,
|
||||
onClearActiveLog: mockOnClearActiveLog,
|
||||
onAddToQuery: mockOnAddToQuery,
|
||||
onGroupByAttribute: mockOnGroupByAttribute,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not open log detail when text is selected', () => {
|
||||
mockIsTextSelected.mockReturnValue(true);
|
||||
|
||||
const { result } = renderHook(() => useLogDetailHandlers());
|
||||
|
||||
act(() => {
|
||||
result.current.handleSetActiveLog(mockLog);
|
||||
});
|
||||
|
||||
expect(mockOnSetActiveLog).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should open log detail when no text is selected', () => {
|
||||
mockIsTextSelected.mockReturnValue(false);
|
||||
|
||||
const { result } = renderHook(() => useLogDetailHandlers());
|
||||
|
||||
act(() => {
|
||||
result.current.handleSetActiveLog(mockLog);
|
||||
});
|
||||
|
||||
expect(mockOnSetActiveLog).toHaveBeenCalledWith(mockLog);
|
||||
});
|
||||
|
||||
it('should toggle off when clicking the same active log', () => {
|
||||
mockIsTextSelected.mockReturnValue(false);
|
||||
|
||||
jest.mocked(useActiveLog).mockReturnValue({
|
||||
activeLog: mockLog,
|
||||
onSetActiveLog: mockOnSetActiveLog,
|
||||
onClearActiveLog: mockOnClearActiveLog,
|
||||
onAddToQuery: mockOnAddToQuery,
|
||||
onGroupByAttribute: mockOnGroupByAttribute,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLogDetailHandlers());
|
||||
|
||||
act(() => {
|
||||
result.current.handleSetActiveLog(mockLog);
|
||||
});
|
||||
|
||||
expect(mockOnClearActiveLog).toHaveBeenCalled();
|
||||
expect(mockOnSetActiveLog).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import { useCallback, useState } from 'react';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import type { UseActiveLog } from 'hooks/logs/types';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useIsTextSelected } from 'hooks/useIsTextSelected';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
type SelectedTab = typeof VIEW_TYPES[keyof typeof VIEW_TYPES] | undefined;
|
||||
@@ -29,13 +28,9 @@ function useLogDetailHandlers({
|
||||
onAddToQuery,
|
||||
} = useActiveLog();
|
||||
const [selectedTab, setSelectedTab] = useState<SelectedTab>(defaultTab);
|
||||
const isTextSelected = useIsTextSelected();
|
||||
|
||||
const handleSetActiveLog = useCallback(
|
||||
(log: ILog, nextTab: SelectedTab = defaultTab): void => {
|
||||
if (isTextSelected()) {
|
||||
return;
|
||||
}
|
||||
if (activeLog?.id === log.id) {
|
||||
onClearActiveLog();
|
||||
setSelectedTab(undefined);
|
||||
@@ -44,7 +39,7 @@ function useLogDetailHandlers({
|
||||
onSetActiveLog(log);
|
||||
setSelectedTab(nextTab ?? defaultTab);
|
||||
},
|
||||
[activeLog?.id, defaultTab, onClearActiveLog, onSetActiveLog, isTextSelected],
|
||||
[activeLog?.id, defaultTab, onClearActiveLog, onSetActiveLog],
|
||||
);
|
||||
|
||||
const handleCloseLogDetail = useCallback((): void => {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useIsTextSelected(): () => boolean {
|
||||
return useCallback((): boolean => {
|
||||
const selection = window.getSelection();
|
||||
return (
|
||||
!!selection && !selection.isCollapsed && selection.toString().length > 0
|
||||
);
|
||||
}, []);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package formatter
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/converter"
|
||||
"github.com/SigNoz/signoz/pkg/converter"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package formatter
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/converter"
|
||||
"github.com/SigNoz/signoz/pkg/converter"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
@@ -78,9 +78,8 @@ func toFixed(value float64, decimals DecimalCount) string {
|
||||
}
|
||||
|
||||
decimalPos := strings.Index(formatted, ".")
|
||||
precision := 0
|
||||
if decimalPos != -1 {
|
||||
precision = len(formatted) - decimalPos - 1
|
||||
precision := len(formatted) - decimalPos - 1
|
||||
if precision < *decimals {
|
||||
return formatted + strings.Repeat("0", *decimals-precision)
|
||||
}
|
||||
@@ -89,8 +88,8 @@ func toFixed(value float64, decimals DecimalCount) string {
|
||||
return formatted
|
||||
}
|
||||
|
||||
func toFixedScaled(value float64, decimals DecimalCount, scaleFormat string) string {
|
||||
return toFixed(value, decimals) + scaleFormat
|
||||
func toFixedScaled(value float64, scaleFormat string) string {
|
||||
return toFixed(value, nil) + scaleFormat
|
||||
}
|
||||
|
||||
func getDecimalsForValue(value float64) int {
|
||||
@@ -13,35 +13,35 @@ func (*throughputFormatter) Name() string {
|
||||
return "throughput"
|
||||
}
|
||||
|
||||
func simpleCountUnit(value float64, decimals *int, symbol string) string {
|
||||
func simpleCountUnit(value float64, symbol string) string {
|
||||
units := []string{"", "K", "M", "B", "T"}
|
||||
scaler := scaledUnits(1000, units, 0)
|
||||
|
||||
return scaler(value, decimals) + " " + symbol
|
||||
return scaler(value, nil) + " " + symbol
|
||||
}
|
||||
|
||||
func (f *throughputFormatter) Format(value float64, unit string) string {
|
||||
switch unit {
|
||||
case "cps", "{count}/s":
|
||||
return simpleCountUnit(value, nil, "c/s")
|
||||
return simpleCountUnit(value, "c/s")
|
||||
case "ops", "{ops}/s":
|
||||
return simpleCountUnit(value, nil, "op/s")
|
||||
return simpleCountUnit(value, "op/s")
|
||||
case "reqps", "{req}/s":
|
||||
return simpleCountUnit(value, nil, "req/s")
|
||||
return simpleCountUnit(value, "req/s")
|
||||
case "rps", "{read}/s":
|
||||
return simpleCountUnit(value, nil, "r/s")
|
||||
return simpleCountUnit(value, "r/s")
|
||||
case "wps", "{write}/s":
|
||||
return simpleCountUnit(value, nil, "w/s")
|
||||
return simpleCountUnit(value, "w/s")
|
||||
case "iops", "{iops}/s":
|
||||
return simpleCountUnit(value, nil, "iops")
|
||||
return simpleCountUnit(value, "iops")
|
||||
case "cpm", "{count}/min":
|
||||
return simpleCountUnit(value, nil, "c/m")
|
||||
return simpleCountUnit(value, "c/m")
|
||||
case "opm", "{ops}/min":
|
||||
return simpleCountUnit(value, nil, "op/m")
|
||||
return simpleCountUnit(value, "op/m")
|
||||
case "rpm", "{read}/min":
|
||||
return simpleCountUnit(value, nil, "r/m")
|
||||
return simpleCountUnit(value, "r/m")
|
||||
case "wpm", "{write}/min":
|
||||
return simpleCountUnit(value, nil, "w/m")
|
||||
return simpleCountUnit(value, "w/m")
|
||||
}
|
||||
// When unit is not matched, return the value as it is.
|
||||
return fmt.Sprintf("%v", value)
|
||||
@@ -46,17 +46,17 @@ func toNanoSeconds(value float64) string {
|
||||
if absValue < 1000 {
|
||||
return toFixed(value, nil) + " ns"
|
||||
} else if absValue < 1000000 { // 2000 ns is better represented as 2 µs
|
||||
return toFixedScaled(value/1000, nil, " µs")
|
||||
return toFixedScaled(value/1000, " µs")
|
||||
} else if absValue < 1000000000 { // 2000000 ns is better represented as 2 ms
|
||||
return toFixedScaled(value/1000000, nil, " ms")
|
||||
return toFixedScaled(value/1000000, " ms")
|
||||
} else if absValue < 60000000000 {
|
||||
return toFixedScaled(value/1000000000, nil, " s")
|
||||
return toFixedScaled(value/1000000000, " s")
|
||||
} else if absValue < 3600000000000 {
|
||||
return toFixedScaled(value/60000000000, nil, " min")
|
||||
return toFixedScaled(value/60000000000, " min")
|
||||
} else if absValue < 86400000000000 {
|
||||
return toFixedScaled(value/3600000000000, nil, " hour")
|
||||
return toFixedScaled(value/3600000000000, " hour")
|
||||
} else {
|
||||
return toFixedScaled(value/86400000000000, nil, " day")
|
||||
return toFixedScaled(value/86400000000000, " day")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,9 @@ func toMicroSeconds(value float64) string {
|
||||
if absValue < 1000 {
|
||||
return toFixed(value, nil) + " µs"
|
||||
} else if absValue < 1000000 { // 2000 µs is better represented as 2 ms
|
||||
return toFixedScaled(value/1000, nil, " ms")
|
||||
return toFixedScaled(value/1000, " ms")
|
||||
} else {
|
||||
return toFixedScaled(value/1000000, nil, " s")
|
||||
return toFixedScaled(value/1000000, " s")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,16 +80,16 @@ func toMilliSeconds(value float64) string {
|
||||
if absValue < 1000 {
|
||||
return toFixed(value, nil) + " ms"
|
||||
} else if absValue < 60000 {
|
||||
return toFixedScaled(value/1000, nil, " s")
|
||||
return toFixedScaled(value/1000, " s")
|
||||
} else if absValue < 3600000 {
|
||||
return toFixedScaled(value/60000, nil, " min")
|
||||
return toFixedScaled(value/60000, " min")
|
||||
} else if absValue < 86400000 { // 172800000 ms is better represented as 2 day
|
||||
return toFixedScaled(value/3600000, nil, " hour")
|
||||
return toFixedScaled(value/3600000, " hour")
|
||||
} else if absValue < 31536000000 {
|
||||
return toFixedScaled(value/86400000, nil, " day")
|
||||
return toFixedScaled(value/86400000, " day")
|
||||
}
|
||||
|
||||
return toFixedScaled(value/31536000000, nil, " year")
|
||||
return toFixedScaled(value/31536000000, " year")
|
||||
}
|
||||
|
||||
// toSeconds returns a easy to read string representation of the given value in seconds
|
||||
@@ -97,24 +97,24 @@ func toSeconds(value float64) string {
|
||||
absValue := math.Abs(value)
|
||||
|
||||
if absValue < 0.000001 {
|
||||
return toFixedScaled(value*1e9, nil, " ns")
|
||||
return toFixedScaled(value*1e9, " ns")
|
||||
} else if absValue < 0.001 {
|
||||
return toFixedScaled(value*1e6, nil, " µs")
|
||||
return toFixedScaled(value*1e6, " µs")
|
||||
} else if absValue < 1 {
|
||||
return toFixedScaled(value*1e3, nil, " ms")
|
||||
return toFixedScaled(value*1e3, " ms")
|
||||
} else if absValue < 60 {
|
||||
return toFixed(value, nil) + " s"
|
||||
} else if absValue < 3600 {
|
||||
return toFixedScaled(value/60, nil, " min")
|
||||
return toFixedScaled(value/60, " min")
|
||||
} else if absValue < 86400 { // 56000 s is better represented as 15.56 hour
|
||||
return toFixedScaled(value/3600, nil, " hour")
|
||||
return toFixedScaled(value/3600, " hour")
|
||||
} else if absValue < 604800 {
|
||||
return toFixedScaled(value/86400, nil, " day")
|
||||
return toFixedScaled(value/86400, " day")
|
||||
} else if absValue < 31536000 {
|
||||
return toFixedScaled(value/604800, nil, " week")
|
||||
return toFixedScaled(value/604800, " week")
|
||||
}
|
||||
|
||||
return toFixedScaled(value/3.15569e7, nil, " year")
|
||||
return toFixedScaled(value/3.15569e7, " year")
|
||||
}
|
||||
|
||||
// toMinutes returns a easy to read string representation of the given value in minutes
|
||||
@@ -124,13 +124,13 @@ func toMinutes(value float64) string {
|
||||
if absValue < 60 {
|
||||
return toFixed(value, nil) + " min"
|
||||
} else if absValue < 1440 {
|
||||
return toFixedScaled(value/60, nil, " hour")
|
||||
return toFixedScaled(value/60, " hour")
|
||||
} else if absValue < 10080 {
|
||||
return toFixedScaled(value/1440, nil, " day")
|
||||
return toFixedScaled(value/1440, " day")
|
||||
} else if absValue < 604800 {
|
||||
return toFixedScaled(value/10080, nil, " week")
|
||||
return toFixedScaled(value/10080, " week")
|
||||
} else {
|
||||
return toFixedScaled(value/5.25948e5, nil, " year")
|
||||
return toFixedScaled(value/5.25948e5, " year")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +142,11 @@ func toHours(value float64) string {
|
||||
if absValue < 24 {
|
||||
return toFixed(value, nil) + " hour"
|
||||
} else if absValue < 168 {
|
||||
return toFixedScaled(value/24, nil, " day")
|
||||
return toFixedScaled(value/24, " day")
|
||||
} else if absValue < 8760 {
|
||||
return toFixedScaled(value/168, nil, " week")
|
||||
return toFixedScaled(value/168, " week")
|
||||
} else {
|
||||
return toFixedScaled(value/8760, nil, " year")
|
||||
return toFixedScaled(value/8760, " year")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,9 +157,9 @@ func toDays(value float64) string {
|
||||
if absValue < 7 {
|
||||
return toFixed(value, nil) + " day"
|
||||
} else if absValue < 365 {
|
||||
return toFixedScaled(value/7, nil, " week")
|
||||
return toFixedScaled(value/7, " week")
|
||||
} else {
|
||||
return toFixedScaled(value/365, nil, " year")
|
||||
return toFixedScaled(value/365, " year")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,6 @@ func toWeeks(value float64) string {
|
||||
if absValue < 52 {
|
||||
return toFixed(value, nil) + " week"
|
||||
} else {
|
||||
return toFixedScaled(value/52, nil, " year")
|
||||
return toFixedScaled(value/52, " year")
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func readAsTimeSeries(rows driver.Rows, queryWindow *qbtypes.TimeRange, step qbt
|
||||
case *time.Time:
|
||||
ts = v.UnixMilli()
|
||||
|
||||
case *float64, *float32, *int64, *int32, *uint64, *uint32, *uint8:
|
||||
case *float64, *float32, *int64, *int32, *uint64, *uint32:
|
||||
val := numericAsFloat(reflect.ValueOf(ptr).Elem().Interface())
|
||||
if m := aggRe.FindStringSubmatch(name); m != nil {
|
||||
id, _ := strconv.Atoi(m[1])
|
||||
@@ -148,7 +148,7 @@ func readAsTimeSeries(rows driver.Rows, queryWindow *qbtypes.TimeRange, step qbt
|
||||
})
|
||||
}
|
||||
|
||||
case **float64, **float32, **int64, **int32, **uint64, **uint32, **uint8:
|
||||
case **float64, **float32, **int64, **int32, **uint64, **uint32:
|
||||
tempVal := reflect.ValueOf(ptr)
|
||||
if tempVal.IsValid() && !tempVal.IsNil() && !tempVal.Elem().IsNil() {
|
||||
val := numericAsFloat(tempVal.Elem().Elem().Interface())
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/formatter"
|
||||
"github.com/SigNoz/signoz/pkg/formatter"
|
||||
"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"
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
|
||||
logsv3 "github.com/SigNoz/signoz/pkg/query-service/app/logs/v3"
|
||||
tracesV4 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v4"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/formatter"
|
||||
"github.com/SigNoz/signoz/pkg/formatter"
|
||||
|
||||
querierV5 "github.com/SigNoz/signoz/pkg/querier"
|
||||
|
||||
|
||||
@@ -26,9 +26,6 @@ const (
|
||||
IncreaseMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, multiIf(row_number() OVER rate_window = 1, nan, (%s - lagInFrame(%s, 1) OVER rate_window) < 0, %s, (%s - lagInFrame(%s, 1) OVER rate_window))) AS per_series_value`
|
||||
|
||||
OthersMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, %s) AS per_series_value`
|
||||
|
||||
RateWithStartTs = "multiIf(row_number() OVER rate_window = 1 AND earliest_start_ts < %d, NAN, row_number() OVER rate_window = 1, sum_all_values / (ts - earliest_start_ts), earliest_start_ts = lagInFrame(latest_start_ts, 1) OVER rate_window, (sum_all_values - lagInFrame(latest_value, 1) OVER rate_window) / (ts - lagInFrame(ts, 1) OVER rate_window), sum_all_values / (ts - lagInFrame(ts, 1) OVER rate_window)) AS per_series_value"
|
||||
IncreaseWithStartTs = "multiIf(row_number() OVER rate_window = 1 AND earliest_start_ts < %d, NAN, row_number() OVER rate_window = 1, sum_all_values, earliest_start_ts = lagInFrame(latest_start_ts, 1) OVER rate_window, sum_all_values - lagInFrame(latest_value, 1) OVER rate_window, sum_all_values) AS per_series_value"
|
||||
)
|
||||
|
||||
type MetricQueryStatementBuilder struct {
|
||||
@@ -333,9 +330,6 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggregationCTE(
|
||||
if query.Aggregations[0].Temporality == metrictypes.Delta {
|
||||
return b.buildTemporalAggDelta(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
} else if query.Aggregations[0].Temporality != metrictypes.Multiple {
|
||||
if query.Aggregations[0].TimeAggregation == metrictypes.TimeAggregationIncrease || query.Aggregations[0].TimeAggregation == metrictypes.TimeAggregationRate {
|
||||
return b.buildTemporalAggCumulativeOrUnspecifiedWithStartTs(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
}
|
||||
return b.buildTemporalAggCumulativeOrUnspecified(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
}
|
||||
return b.buildTemporalAggForMultipleTemporalities(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
@@ -388,115 +382,6 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggDelta(
|
||||
return fmt.Sprintf("__temporal_aggregation_cte AS (%s)", q), args, nil
|
||||
}
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecifiedWithStartTs(
|
||||
ctx context.Context,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
timeSeriesCTE string,
|
||||
timeSeriesCTEArgs []any,
|
||||
) (string, []any, error) {
|
||||
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
if tbl == SamplesV4TableName {
|
||||
return b.buildTemporalAggCumulativeOrUnspecifiedWithStartTsForSamplesTbl(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
}
|
||||
return b.buildTemporalAggCumulativeOrUnspecified(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
}
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecifiedWithStartTsForSamplesTbl(
|
||||
_ context.Context,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
timeSeriesCTE string,
|
||||
timeSeriesCTEArgs []any,
|
||||
) (string, []any, error) {
|
||||
stepSec := int64(query.StepInterval.Seconds())
|
||||
|
||||
moreInfoQueryBuilder := sqlbuilder.NewSelectBuilder()
|
||||
moreInfoQueryBuilder.Select("fingerprint")
|
||||
moreInfoQueryBuilder.SelectMore(fmt.Sprintf(
|
||||
"toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), toIntervalSecond(%d)) AS ts",
|
||||
stepSec,
|
||||
))
|
||||
moreInfoQueryBuilder.SelectMore("toDateTime(intDiv(start_timestamp_unix_milli, 1000)) AS start_ts")
|
||||
for _, g := range query.GroupBy {
|
||||
moreInfoQueryBuilder.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
|
||||
aggCol, err := AggregationColumnForSamplesTable(start, end, query.Aggregations[0].Type, query.Aggregations[0].Temporality, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
moreInfoQueryBuilder.SelectMore(fmt.Sprintf("%s AS max_value", aggCol))
|
||||
|
||||
colWithLatestValue, err := AggregationColumnForSamplesTable(start, end, query.Aggregations[0].Type, query.Aggregations[0].Temporality, metrictypes.TimeAggregationLatest, query.Aggregations[0].TableHints)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
moreInfoQueryBuilder.SelectMore(fmt.Sprintf("%s AS latest_value", colWithLatestValue))
|
||||
moreInfoQueryBuilder.SelectMore("max(unix_milli) AS latest_timestamp")
|
||||
|
||||
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
moreInfoQueryBuilder.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
|
||||
moreInfoQueryBuilder.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
|
||||
moreInfoQueryBuilder.Where(
|
||||
moreInfoQueryBuilder.In("metric_name", query.Aggregations[0].MetricName),
|
||||
moreInfoQueryBuilder.GTE("unix_milli", start),
|
||||
moreInfoQueryBuilder.LT("unix_milli", end),
|
||||
)
|
||||
moreInfoQueryBuilder.GroupBy("fingerprint", "ts", "start_ts")
|
||||
moreInfoQueryBuilder.GroupBy(querybuilder.GroupByKeys(query.GroupBy)...)
|
||||
|
||||
moreInfoPerRowQuery, moreInfoPerRowArgs := moreInfoQueryBuilder.BuildWithFlavor(sqlbuilder.ClickHouse, timeSeriesCTEArgs...)
|
||||
|
||||
innerQueryBuilder := sqlbuilder.NewSelectBuilder()
|
||||
innerQueryBuilder.Select("fingerprint")
|
||||
innerQueryBuilder.SelectMore("ts")
|
||||
for _, g := range query.GroupBy {
|
||||
innerQueryBuilder.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
innerQueryBuilder.SelectMore("max(start_ts) AS latest_start_ts")
|
||||
innerQueryBuilder.SelectMore("min(start_ts) AS earliest_start_ts")
|
||||
innerQueryBuilder.SelectMore("sum(max_value) AS sum_all_values")
|
||||
innerQueryBuilder.SelectMore("argMax(latest_value, latest_timestamp) AS latest_value")
|
||||
|
||||
innerQueryBuilder.From(fmt.Sprintf("(%s)", moreInfoPerRowQuery))
|
||||
|
||||
innerQueryBuilder.GroupBy("fingerprint", "ts")
|
||||
innerQueryBuilder.GroupBy(querybuilder.GroupByKeys(query.GroupBy)...)
|
||||
|
||||
innerQuery, innerQueryArgs := innerQueryBuilder.BuildWithFlavor(sqlbuilder.ClickHouse, moreInfoPerRowArgs...)
|
||||
|
||||
switch query.Aggregations[0].TimeAggregation {
|
||||
case metrictypes.TimeAggregationRate:
|
||||
rateExpr := fmt.Sprintf(RateWithStartTs, start)
|
||||
wrapped := sqlbuilder.NewSelectBuilder()
|
||||
wrapped.Select("ts")
|
||||
wrapped.SelectMore("latest_value")
|
||||
for _, g := range query.GroupBy {
|
||||
wrapped.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
wrapped.SelectMore(rateExpr)
|
||||
wrapped.From(fmt.Sprintf("(%s) WINDOW rate_window AS (PARTITION BY fingerprint ORDER BY fingerprint, ts)", innerQuery))
|
||||
q, args := wrapped.BuildWithFlavor(sqlbuilder.ClickHouse, moreInfoPerRowArgs...)
|
||||
return fmt.Sprintf("__temporal_aggregation_cte AS (%s)", q), args, nil
|
||||
|
||||
case metrictypes.TimeAggregationIncrease:
|
||||
incExpr := fmt.Sprintf(IncreaseWithStartTs, start)
|
||||
wrapped := sqlbuilder.NewSelectBuilder()
|
||||
wrapped.Select("ts")
|
||||
wrapped.SelectMore("latest_value")
|
||||
for _, g := range query.GroupBy {
|
||||
wrapped.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
wrapped.SelectMore(incExpr)
|
||||
wrapped.From(fmt.Sprintf("(%s) WINDOW rate_window AS (PARTITION BY fingerprint ORDER BY fingerprint, ts)", innerQuery))
|
||||
q, args := wrapped.BuildWithFlavor(sqlbuilder.ClickHouse, moreInfoPerRowArgs...)
|
||||
return fmt.Sprintf("__temporal_aggregation_cte AS (%s)", q), args, nil
|
||||
default:
|
||||
return fmt.Sprintf("__temporal_aggregation_cte AS (%s)", innerQuery), innerQueryArgs, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
_ context.Context,
|
||||
start, end uint64,
|
||||
@@ -504,7 +389,6 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
timeSeriesCTE string,
|
||||
timeSeriesCTEArgs []any,
|
||||
) (string, []any, error) {
|
||||
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
stepSec := int64(query.StepInterval.Seconds())
|
||||
|
||||
baseSb := sqlbuilder.NewSelectBuilder()
|
||||
@@ -516,9 +400,6 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
for _, g := range query.GroupBy {
|
||||
baseSb.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
if tbl != SamplesV4TableName {
|
||||
baseSb.SelectMore("uniqMerge(num_start_timestamps) AS num_start_timestamps")
|
||||
}
|
||||
|
||||
aggCol, err := AggregationColumnForSamplesTable(start, end, query.Aggregations[0].Type, query.Aggregations[0].Temporality, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
if err != nil {
|
||||
@@ -526,6 +407,7 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
}
|
||||
baseSb.SelectMore(fmt.Sprintf("%s AS per_series_value", aggCol))
|
||||
|
||||
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
baseSb.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
|
||||
baseSb.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
|
||||
baseSb.Where(
|
||||
@@ -546,9 +428,6 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
for _, g := range query.GroupBy {
|
||||
wrapped.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
if tbl != SamplesV4TableName {
|
||||
wrapped.SelectMore("num_start_timestamps > 1 AS has_restarts")
|
||||
}
|
||||
wrapped.SelectMore(fmt.Sprintf("%s AS per_series_value", RateTmpl))
|
||||
wrapped.From(fmt.Sprintf("(%s) WINDOW rate_window AS (PARTITION BY fingerprint ORDER BY fingerprint, ts)", innerQuery))
|
||||
q, args := wrapped.BuildWithFlavor(sqlbuilder.ClickHouse, innerArgs...)
|
||||
@@ -560,9 +439,6 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
for _, g := range query.GroupBy {
|
||||
wrapped.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
if tbl != SamplesV4TableName {
|
||||
wrapped.SelectMore("num_start_timestamps > 1 AS has_restarts")
|
||||
}
|
||||
wrapped.SelectMore(fmt.Sprintf("%s AS per_series_value", IncreaseTmpl))
|
||||
wrapped.From(fmt.Sprintf("(%s) WINDOW rate_window AS (PARTITION BY fingerprint ORDER BY fingerprint, ts)", innerQuery))
|
||||
q, args := wrapped.BuildWithFlavor(sqlbuilder.ClickHouse, innerArgs...)
|
||||
@@ -639,12 +515,11 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggForMultipleTemporalities(
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildSpatialAggregationCTE(
|
||||
_ context.Context,
|
||||
start uint64,
|
||||
end uint64,
|
||||
_ uint64,
|
||||
_ uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
_ map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
) (string, []any, error) {
|
||||
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
if query.Aggregations[0].SpaceAggregation.IsZero() {
|
||||
return "", nil, errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
@@ -659,9 +534,6 @@ func (b *MetricQueryStatementBuilder) buildSpatialAggregationCTE(
|
||||
sb.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
sb.SelectMore(fmt.Sprintf("%s(per_series_value) AS value", query.Aggregations[0].SpaceAggregation.StringValue()))
|
||||
if tbl != SamplesV4TableName {
|
||||
sb.SelectMore("max(has_restarts) AS has_restarts")
|
||||
}
|
||||
sb.From("__temporal_aggregation_cte")
|
||||
sb.Where(sb.EQ("isNaN(per_series_value)", 0))
|
||||
if query.Aggregations[0].ValueFilter != nil {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/converter"
|
||||
"github.com/SigNoz/signoz/pkg/converter"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
Reference in New Issue
Block a user