mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-15 10:50:33 +01:00
Compare commits
5 Commits
issue_5674
...
feat/rules
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6218e27ec2 | ||
|
|
16c1fce326 | ||
|
|
06b762b7ef | ||
|
|
466edf1f1c | ||
|
|
849353b5c0 |
@@ -4259,6 +4259,34 @@ components:
|
||||
type: number
|
||||
clusterName:
|
||||
type: string
|
||||
counts:
|
||||
properties:
|
||||
daemonSets:
|
||||
format: int64
|
||||
type: integer
|
||||
deployments:
|
||||
format: int64
|
||||
type: integer
|
||||
jobs:
|
||||
format: int64
|
||||
type: integer
|
||||
namespaces:
|
||||
format: int64
|
||||
type: integer
|
||||
nodes:
|
||||
format: int64
|
||||
type: integer
|
||||
statefulSets:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- nodes
|
||||
- namespaces
|
||||
- deployments
|
||||
- daemonSets
|
||||
- jobs
|
||||
- statefulSets
|
||||
type: object
|
||||
meta:
|
||||
additionalProperties:
|
||||
type: string
|
||||
@@ -4279,6 +4307,7 @@ components:
|
||||
- nodeCountsByReadiness
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- counts
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesClusters:
|
||||
@@ -4800,6 +4829,26 @@ components:
|
||||
type: object
|
||||
InframonitoringtypesNamespaceRecord:
|
||||
properties:
|
||||
counts:
|
||||
properties:
|
||||
daemonSets:
|
||||
format: int64
|
||||
type: integer
|
||||
deployments:
|
||||
format: int64
|
||||
type: integer
|
||||
jobs:
|
||||
format: int64
|
||||
type: integer
|
||||
statefulSets:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- deployments
|
||||
- daemonSets
|
||||
- jobs
|
||||
- statefulSets
|
||||
type: object
|
||||
meta:
|
||||
additionalProperties:
|
||||
type: string
|
||||
@@ -4823,6 +4872,7 @@ components:
|
||||
- namespaceMemory
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- counts
|
||||
- meta
|
||||
type: object
|
||||
InframonitoringtypesNamespaces:
|
||||
@@ -7365,6 +7415,7 @@ components:
|
||||
queries:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryEnvelope'
|
||||
minItems: 1
|
||||
nullable: true
|
||||
type: array
|
||||
queryType:
|
||||
@@ -7551,35 +7602,28 @@ components:
|
||||
type: string
|
||||
disabled:
|
||||
type: boolean
|
||||
evalWindow:
|
||||
type: string
|
||||
evaluation:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationEnvelope'
|
||||
frequency:
|
||||
type: string
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
notificationSettings:
|
||||
$ref: '#/components/schemas/RuletypesNotificationSettings'
|
||||
preferredChannels:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
ruleType:
|
||||
$ref: '#/components/schemas/RuletypesRuleType'
|
||||
schemaVersion:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
version:
|
||||
enum:
|
||||
- v2alpha1
|
||||
type: string
|
||||
required:
|
||||
- alert
|
||||
- alertType
|
||||
- ruleType
|
||||
- condition
|
||||
- schemaVersion
|
||||
- evaluation
|
||||
- notificationSettings
|
||||
type: object
|
||||
RuletypesQueryType:
|
||||
enum:
|
||||
@@ -7629,12 +7673,8 @@ components:
|
||||
type: string
|
||||
disabled:
|
||||
type: boolean
|
||||
evalWindow:
|
||||
type: string
|
||||
evaluation:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationEnvelope'
|
||||
frequency:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
labels:
|
||||
@@ -7643,15 +7683,11 @@ components:
|
||||
type: object
|
||||
notificationSettings:
|
||||
$ref: '#/components/schemas/RuletypesNotificationSettings'
|
||||
preferredChannels:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
ruleType:
|
||||
$ref: '#/components/schemas/RuletypesRuleType'
|
||||
schemaVersion:
|
||||
type: string
|
||||
source:
|
||||
enum:
|
||||
- v2alpha1
|
||||
type: string
|
||||
state:
|
||||
$ref: '#/components/schemas/RuletypesAlertState'
|
||||
@@ -7660,8 +7696,6 @@ components:
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- state
|
||||
@@ -7669,6 +7703,9 @@ components:
|
||||
- alertType
|
||||
- ruleType
|
||||
- condition
|
||||
- schemaVersion
|
||||
- evaluation
|
||||
- notificationSettings
|
||||
type: object
|
||||
RuletypesRuleCondition:
|
||||
properties:
|
||||
@@ -7681,10 +7718,6 @@ components:
|
||||
type: string
|
||||
compositeQuery:
|
||||
$ref: '#/components/schemas/RuletypesAlertCompositeQuery'
|
||||
matchType:
|
||||
$ref: '#/components/schemas/RuletypesMatchType'
|
||||
op:
|
||||
$ref: '#/components/schemas/RuletypesCompareOperator'
|
||||
requireMinPoints:
|
||||
type: boolean
|
||||
requiredNumPoints:
|
||||
@@ -7693,16 +7726,12 @@ components:
|
||||
$ref: '#/components/schemas/RuletypesSeasonality'
|
||||
selectedQueryName:
|
||||
type: string
|
||||
target:
|
||||
format: double
|
||||
nullable: true
|
||||
type: number
|
||||
targetUnit:
|
||||
type: string
|
||||
thresholds:
|
||||
$ref: '#/components/schemas/RuletypesRuleThresholdData'
|
||||
required:
|
||||
- compositeQuery
|
||||
- thresholds
|
||||
- selectedQueryName
|
||||
type: object
|
||||
RuletypesRuleThresholdData:
|
||||
discriminator:
|
||||
|
||||
@@ -5669,6 +5669,39 @@ export interface InframonitoringtypesChecksDTO {
|
||||
type: InframonitoringtypesCheckTypeDTO;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesClusterRecordDTOCounts = {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
daemonSets: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
deployments: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
jobs: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
namespaces: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
nodes: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
statefulSets: number;
|
||||
};
|
||||
|
||||
export type InframonitoringtypesClusterRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -5813,6 +5846,10 @@ export interface InframonitoringtypesClusterRecordDTO {
|
||||
* @type string
|
||||
*/
|
||||
clusterName: string;
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
counts: InframonitoringtypesClusterRecordDTOCounts;
|
||||
/**
|
||||
* @type object,null
|
||||
*/
|
||||
@@ -6368,6 +6405,29 @@ export interface InframonitoringtypesJobsDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesNamespaceRecordDTOCounts = {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
daemonSets: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
deployments: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
jobs: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
statefulSets: number;
|
||||
};
|
||||
|
||||
export type InframonitoringtypesNamespaceRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6379,6 +6439,10 @@ export type InframonitoringtypesNamespaceRecordDTOMeta =
|
||||
InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesNamespaceRecordDTO {
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
counts: InframonitoringtypesNamespaceRecordDTOCounts;
|
||||
/**
|
||||
* @type object,null
|
||||
*/
|
||||
|
||||
@@ -89,21 +89,20 @@ describe('UnpricedModelsTab (integration)', () => {
|
||||
await screen.findByTestId(`unpriced-model-name-${MODEL}`);
|
||||
await selectRule(user, MODEL, 'rule-openai');
|
||||
|
||||
const trigger = screen.getByTestId(`map-to-select-${MODEL}`);
|
||||
expect(
|
||||
within(trigger).getByText('openai:gpt-4o ($3.00/$9.00)'),
|
||||
within(screen.getByTestId(`map-to-select-${MODEL}`)).getByText(
|
||||
'openai:gpt-4o ($3.00/$9.00)',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await user.click(await screen.findByTestId('unpriced-map-cancel-btn'));
|
||||
|
||||
await waitFor(() =>
|
||||
await waitFor(() => {
|
||||
const trigger = screen.getByTestId(`map-to-select-${MODEL}`);
|
||||
expect(
|
||||
within(trigger).queryByText('openai:gpt-4o ($3.00/$9.00)'),
|
||||
).not.toBeInTheDocument(),
|
||||
);
|
||||
expect(
|
||||
within(trigger).getByText('Select / Create a pricing model'),
|
||||
).toBeInTheDocument();
|
||||
within(trigger).getByText('Select / Create a pricing model'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('commits the mapping in one request when confirmed', async () => {
|
||||
|
||||
@@ -191,14 +191,6 @@
|
||||
min-height: 0;
|
||||
overflow-y: visible;
|
||||
|
||||
.time-series-view-container-header {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.time-series-view {
|
||||
flex-shrink: 0;
|
||||
height: 65vh;
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
getListQuery,
|
||||
getQueryByPanelType,
|
||||
} from 'container/LogsExplorerViews/explorerUtils';
|
||||
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters/BuilderUnitsFilter';
|
||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||
@@ -461,18 +460,17 @@ function LogsExplorerViewsContainer({
|
||||
)}
|
||||
{selectedPanelType === PANEL_TYPES.TIME_SERIES && !showLiveLogs && (
|
||||
<div className="time-series-view-container">
|
||||
<div className="time-series-view-container-header">
|
||||
<BuilderUnitsFilter onChange={onUnitChange} yAxisUnit={yAxisUnit} />
|
||||
</div>
|
||||
<TimeSeriesView
|
||||
isLoading={isLoading || isFetching}
|
||||
data={data}
|
||||
isError={isError}
|
||||
error={error as APIError}
|
||||
yAxisUnit={yAxisUnit}
|
||||
onYAxisUnitChange={onUnitChange}
|
||||
isFilterApplied={!isEmpty(listQuery?.filters?.items)}
|
||||
dataSource={DataSource.LOGS}
|
||||
setWarning={setWarning}
|
||||
allowExport
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -311,6 +311,7 @@ function TimeSeries({
|
||||
dataSource={DataSource.METRICS}
|
||||
error={queries[index].error as APIError}
|
||||
setWarning={setWarning}
|
||||
allowExport
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
min-height: 350px;
|
||||
padding: 0px 12px;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: 50vh;
|
||||
min-height: 350px;
|
||||
|
||||
@@ -16,6 +16,7 @@ import Uplot from 'components/Uplot';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch';
|
||||
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters/BuilderUnitsFilter';
|
||||
import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/GridCard/utils';
|
||||
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
||||
import EmptyMetricsSearch from 'container/MetricsExplorer/Explorer/EmptyMetricsSearch';
|
||||
@@ -41,11 +42,14 @@ import { SuccessResponse, Warning } from 'types/api';
|
||||
import { LegendPosition } from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { QueryRangeResponseV5 } from 'types/api/v5/queryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import TimeseriesExportMenu from './TimeseriesExportMenu';
|
||||
|
||||
import './TimeSeriesView.styles.scss';
|
||||
|
||||
function TimeSeriesView({
|
||||
@@ -59,6 +63,8 @@ function TimeSeriesView({
|
||||
setWarning,
|
||||
panelType = PANEL_TYPES.TIME_SERIES,
|
||||
stackBarChart = false,
|
||||
allowExport = false,
|
||||
onYAxisUnitChange,
|
||||
}: TimeSeriesViewProps): JSX.Element {
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -244,10 +250,33 @@ function TimeSeriesView({
|
||||
[baseChartOptions, stackedBands],
|
||||
);
|
||||
|
||||
const showExport = allowExport && !!data?.rawV5Response;
|
||||
const showHeader = showExport || !!onYAxisUnitChange;
|
||||
|
||||
return (
|
||||
<div className="time-series-view">
|
||||
{isError && error && <ErrorInPlace error={error as APIError} />}
|
||||
|
||||
{showHeader && (
|
||||
<div className="time-series-view__header">
|
||||
<div>
|
||||
{onYAxisUnitChange && (
|
||||
<BuilderUnitsFilter onChange={onYAxisUnitChange} yAxisUnit={yAxisUnit} />
|
||||
)}
|
||||
</div>
|
||||
{showExport && data?.rawV5Response && (
|
||||
<TimeseriesExportMenu
|
||||
dataSource={dataSource}
|
||||
yAxisUnit={yAxisUnit}
|
||||
queryResponse={data.rawV5Response}
|
||||
query={currentQuery}
|
||||
legendMap={data.legendMap}
|
||||
fileName={`${dataSource}-timeseries`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="graph-container"
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
@@ -295,7 +324,11 @@ function TimeSeriesView({
|
||||
}
|
||||
|
||||
interface TimeSeriesViewProps {
|
||||
data?: SuccessResponse<MetricRangePayloadProps> & { warning?: Warning };
|
||||
data?: SuccessResponse<MetricRangePayloadProps> & {
|
||||
warning?: Warning;
|
||||
rawV5Response?: QueryRangeResponseV5;
|
||||
legendMap?: Record<string, string>;
|
||||
};
|
||||
yAxisUnit?: string;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
@@ -305,6 +338,11 @@ interface TimeSeriesViewProps {
|
||||
setWarning?: Dispatch<SetStateAction<Warning | undefined>>;
|
||||
panelType?: PANEL_TYPES;
|
||||
stackBarChart?: boolean;
|
||||
// Opt-in: render the client-side export menu (Logs explorer for now).
|
||||
allowExport?: boolean;
|
||||
// Opt-in: render the y-axis unit selector in the header (views without their
|
||||
// own selector, e.g. Logs). Metrics keeps its separate YAxisUnitSelector.
|
||||
onYAxisUnitChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
TimeSeriesView.defaultProps = {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
.timeseries-export-popover {
|
||||
width: 240px;
|
||||
padding: 0 12px 12px 12px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: var(--periscope-font-size-small);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.88px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.export-format {
|
||||
padding: 12px 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// radio option labels — radix popover content inherits the root font
|
||||
// size; pin to the app's 13px base the antd popover used to impose
|
||||
label {
|
||||
font-size: var(--periscope-font-size-base);
|
||||
}
|
||||
}
|
||||
|
||||
.export-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Download } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
|
||||
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useClientExport } from 'hooks/useExportData/useClientExport';
|
||||
import { ExportFormat } from 'lib/exportData/types';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryRangeResponseV5 } from 'types/api/v5/queryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import './TimeseriesExportMenu.styles.scss';
|
||||
|
||||
interface TimeseriesExportMenuProps {
|
||||
dataSource: DataSource;
|
||||
queryResponse: QueryRangeResponseV5;
|
||||
query?: Query;
|
||||
yAxisUnit?: string;
|
||||
legendMap?: Record<string, string>;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
// Download menu for in-memory timeseries data (client-side serialization).
|
||||
// The raw/list backend export keeps its own menu in DownloadOptionsMenu.
|
||||
export default function TimeseriesExportMenu({
|
||||
dataSource,
|
||||
queryResponse,
|
||||
query,
|
||||
yAxisUnit,
|
||||
legendMap,
|
||||
fileName,
|
||||
}: TimeseriesExportMenuProps): JSX.Element {
|
||||
const [exportFormat, setExportFormat] = useState<string>(ExportFormat.Csv);
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
|
||||
const { isExporting, handleExport: handleClientExport } = useClientExport({
|
||||
response: queryResponse,
|
||||
query,
|
||||
yAxisUnit,
|
||||
legendMap,
|
||||
fileName,
|
||||
});
|
||||
|
||||
const handleExport = useCallback((): void => {
|
||||
setIsPopoverOpen(false);
|
||||
handleClientExport({ format: exportFormat as ExportFormat });
|
||||
}, [exportFormat, handleClientExport]);
|
||||
|
||||
return (
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||
<TooltipSimple title="Download">
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
aria-label="Download"
|
||||
data-testid={`timeseries-export-${dataSource}`}
|
||||
disabled={isExporting}
|
||||
loading={isExporting}
|
||||
>
|
||||
<Download size={14} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipSimple>
|
||||
<PopoverContent align="end" className="timeseries-export-popover">
|
||||
<div className="export-format">
|
||||
<Typography.Text className="title">FORMAT</Typography.Text>
|
||||
<RadioGroup value={exportFormat} onChange={setExportFormat}>
|
||||
<RadioGroupItem value={ExportFormat.Csv}>csv</RadioGroupItem>
|
||||
<RadioGroupItem value={ExportFormat.Jsonl}>jsonl</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
className="export-button"
|
||||
onClick={handleExport}
|
||||
disabled={isExporting}
|
||||
loading={isExporting}
|
||||
prefix={<Download size={16} />}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import store from 'store';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import TimeSeriesView from '../TimeSeriesView';
|
||||
|
||||
jest.mock('components/Uplot', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="uplot-chart" />,
|
||||
}));
|
||||
|
||||
jest.mock('../TimeseriesExportMenu', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="timeseries-export-menu" />,
|
||||
}));
|
||||
|
||||
jest.mock('container/QueryBuilder/filters/BuilderUnitsFilter', () => ({
|
||||
BuilderUnitsFilter: (): JSX.Element => (
|
||||
<div data-testid="builder-units-filter" />
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: (): unknown => ({ currentQuery: null }),
|
||||
}));
|
||||
|
||||
jest.mock('lib/uPlotLib/getUplotChartOptions', () => ({
|
||||
getUPlotChartOptions: (): unknown => ({}),
|
||||
}));
|
||||
|
||||
jest.mock('lib/uPlotLib/utils/getUplotChartData', () => ({
|
||||
getUPlotChartData: (): number[][] => [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'container/DashboardContainer/visualization/charts/utils/stackSeriesUtils',
|
||||
() => ({ stackSeries: (): unknown => ({ data: [], bands: [] }) }),
|
||||
);
|
||||
|
||||
jest.mock('container/GridCardLayout/GridCard/utils', () => ({
|
||||
getLocalStorageGraphVisibilityState: (): unknown => ({
|
||||
graphVisibilityStates: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('providers/Timezone', () => ({
|
||||
useTimezone: (): unknown => ({ timezone: { value: 'UTC' } }),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useDimensions', () => ({
|
||||
useResizeObserver: (): unknown => ({ width: 800, height: 400 }),
|
||||
}));
|
||||
|
||||
jest.mock('api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockStore = configureStore([])({ ...store.getState() });
|
||||
|
||||
const rawV5Response = {
|
||||
type: 'time_series',
|
||||
data: { results: [] },
|
||||
meta: {},
|
||||
};
|
||||
|
||||
function makeData(withRawV5: boolean): any {
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: { data: { result: [], resultType: '' } },
|
||||
...(withRawV5 ? { rawV5Response, legendMap: {} } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function renderView(props: {
|
||||
allowExport?: boolean;
|
||||
withRawV5?: boolean;
|
||||
onYAxisUnitChange?: (value: string) => void;
|
||||
}): ReturnType<typeof render> {
|
||||
const { allowExport, withRawV5 = true, onYAxisUnitChange } = props;
|
||||
return render(
|
||||
<Provider store={mockStore}>
|
||||
<MemoryRouter>
|
||||
<TimeSeriesView
|
||||
isLoading={false}
|
||||
isError={false}
|
||||
isFilterApplied
|
||||
dataSource={DataSource.LOGS}
|
||||
data={makeData(withRawV5)}
|
||||
allowExport={allowExport}
|
||||
onYAxisUnitChange={onYAxisUnitChange}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('TimeSeriesView header gating', () => {
|
||||
it('renders the export menu when allowExport is set and raw V5 data is present', () => {
|
||||
const { queryByTestId } = renderView({ allowExport: true });
|
||||
expect(queryByTestId('timeseries-export-menu')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders no export menu without allowExport', () => {
|
||||
const { queryByTestId } = renderView({});
|
||||
expect(queryByTestId('timeseries-export-menu')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders no export menu when the raw V5 response is missing', () => {
|
||||
const { queryByTestId } = renderView({ allowExport: true, withRawV5: false });
|
||||
expect(queryByTestId('timeseries-export-menu')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the unit selector only when onYAxisUnitChange is passed', () => {
|
||||
const withUnit = renderView({ onYAxisUnitChange: jest.fn() });
|
||||
expect(withUnit.queryByTestId('builder-units-filter')).toBeInTheDocument();
|
||||
withUnit.unmount();
|
||||
|
||||
const withoutUnit = renderView({ allowExport: true });
|
||||
expect(
|
||||
withoutUnit.queryByTestId('builder-units-filter'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders no header row when neither export nor unit selector is enabled', () => {
|
||||
const { container } = renderView({ withRawV5: false });
|
||||
expect(container.querySelector('.time-series-view__header')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import { fireEvent, render, screen } from 'tests/test-utils';
|
||||
import { QueryRangeResponseV5 } from 'types/api/v5/queryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import TimeseriesExportMenu from '../TimeseriesExportMenu';
|
||||
|
||||
const mockHandleExport = jest.fn();
|
||||
let mockIsExporting = false;
|
||||
|
||||
jest.mock('hooks/useExportData/useClientExport', () => ({
|
||||
useClientExport: (): unknown => ({
|
||||
isExporting: mockIsExporting,
|
||||
handleExport: mockHandleExport,
|
||||
}),
|
||||
}));
|
||||
|
||||
const response = {
|
||||
type: 'time_series',
|
||||
data: { results: [] },
|
||||
meta: {},
|
||||
} as unknown as QueryRangeResponseV5;
|
||||
|
||||
const TEST_ID = `timeseries-export-${DataSource.LOGS}`;
|
||||
|
||||
function renderMenu(): void {
|
||||
render(
|
||||
<TimeseriesExportMenu
|
||||
dataSource={DataSource.LOGS}
|
||||
queryResponse={response}
|
||||
fileName="logs-timeseries"
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('TimeseriesExportMenu', () => {
|
||||
beforeEach(() => {
|
||||
mockHandleExport.mockReset();
|
||||
mockIsExporting = false;
|
||||
});
|
||||
|
||||
it('renders the download trigger button', () => {
|
||||
renderMenu();
|
||||
expect(screen.getByTestId(TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows only format options — no shape, row-count, or column controls', () => {
|
||||
renderMenu();
|
||||
fireEvent.click(screen.getByTestId(TEST_ID));
|
||||
|
||||
expect(screen.getByText('FORMAT')).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'csv' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'jsonl' })).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText('Number of Rows')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Columns')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('radio', { name: 'long' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('radio', { name: 'wide' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('exports as csv by default', () => {
|
||||
renderMenu();
|
||||
fireEvent.click(screen.getByTestId(TEST_ID));
|
||||
fireEvent.click(screen.getByText('Export'));
|
||||
|
||||
expect(mockHandleExport).toHaveBeenCalledTimes(1);
|
||||
expect(mockHandleExport).toHaveBeenCalledWith({ format: 'csv' });
|
||||
});
|
||||
|
||||
it('exports as jsonl when selected', () => {
|
||||
renderMenu();
|
||||
fireEvent.click(screen.getByTestId(TEST_ID));
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'jsonl' }));
|
||||
fireEvent.click(screen.getByText('Export'));
|
||||
|
||||
expect(mockHandleExport).toHaveBeenCalledWith({ format: 'jsonl' });
|
||||
});
|
||||
|
||||
it('disables the trigger while an export is in progress', () => {
|
||||
mockIsExporting = true;
|
||||
renderMenu();
|
||||
|
||||
expect(screen.getByTestId(TEST_ID)).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,41 @@
|
||||
import { SuccessResponse } from 'types/api/index';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { QueryRangeResponseV5, TimeSeriesData } from 'types/api/v5/queryRange';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
|
||||
type ConvertibleData = SuccessResponse<MetricRangePayloadProps> & {
|
||||
rawV5Response?: QueryRangeResponseV5;
|
||||
};
|
||||
|
||||
// Applies the same ns→ms conversion to the raw V5 tree, so client-side export
|
||||
// serializes the values the chart displays (not the original nanoseconds).
|
||||
function convertRawV5ValuesToMs(
|
||||
response: QueryRangeResponseV5,
|
||||
): QueryRangeResponseV5 {
|
||||
if (response.type !== 'time_series') {
|
||||
return response;
|
||||
}
|
||||
|
||||
const results = (response.data.results as TimeSeriesData[]).map((result) => ({
|
||||
...result,
|
||||
aggregations: (result.aggregations ?? []).map((bucket) => ({
|
||||
...bucket,
|
||||
series: (bucket.series ?? []).map((series) => ({
|
||||
...series,
|
||||
values: (series.values ?? []).map((value) => ({
|
||||
...value,
|
||||
value: value.value / 1000000,
|
||||
})),
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
return { ...response, data: { ...response.data, results } };
|
||||
}
|
||||
|
||||
export const convertDataValueToMs = (
|
||||
data?: SuccessResponse<MetricRangePayloadProps>,
|
||||
): SuccessResponse<MetricRangePayloadProps> | undefined => {
|
||||
data?: ConvertibleData,
|
||||
): ConvertibleData | undefined => {
|
||||
const convertedData = data;
|
||||
|
||||
const convertedResult: QueryData[] = data?.payload?.data?.result
|
||||
@@ -22,5 +53,11 @@ export const convertDataValueToMs = (
|
||||
convertedData.payload.data.result = convertedResult;
|
||||
}
|
||||
|
||||
if (convertedData?.rawV5Response) {
|
||||
convertedData.rawV5Response = convertRawV5ValuesToMs(
|
||||
convertedData.rawV5Response,
|
||||
);
|
||||
}
|
||||
|
||||
return convertedData;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,11 @@ import { SuccessResponseV2, Warning } from 'types/api';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { ExecStats, MetricRangePayloadV5 } from 'types/api/v5/queryRange';
|
||||
import {
|
||||
ExecStats,
|
||||
MetricRangePayloadV5,
|
||||
QueryRangeResponseV5,
|
||||
} from 'types/api/v5/queryRange';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -192,6 +196,8 @@ export async function GetMetricQueryRange(
|
||||
| SuccessResponseV2<MetricRangePayloadV5>;
|
||||
let warning: Warning | undefined;
|
||||
let meta: ExecStats | undefined;
|
||||
// Raw V5 response, kept before it's converted to legacy — powers client-side export.
|
||||
let rawV5Response: QueryRangeResponseV5 | undefined;
|
||||
|
||||
const panelType = props.originalGraphType || props.graphType;
|
||||
|
||||
@@ -268,6 +274,8 @@ export async function GetMetricQueryRange(
|
||||
endTime: props.end * 1000,
|
||||
});
|
||||
|
||||
rawV5Response = publicResponse.data.data;
|
||||
|
||||
// Convert V5 response to legacy format for components
|
||||
response = convertV5ResponseToLegacy(
|
||||
{
|
||||
@@ -288,6 +296,8 @@ export async function GetMetricQueryRange(
|
||||
headers,
|
||||
);
|
||||
|
||||
rawV5Response = v5Response.data.data;
|
||||
|
||||
// Convert V5 response to legacy format for components
|
||||
response = convertV5ResponseToLegacy(
|
||||
{
|
||||
@@ -366,6 +376,8 @@ export async function GetMetricQueryRange(
|
||||
...response,
|
||||
warning,
|
||||
meta,
|
||||
rawV5Response,
|
||||
legendMap,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,18 @@ describe('exportTimeseriesData', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('omits display-only format ids (short/none) from headers', () => {
|
||||
const data = [
|
||||
makeQuery('A', [{ series: [makeSeries({ service: 'a' }, [[1000, 1]])] }]),
|
||||
];
|
||||
|
||||
const short = exportTimeseriesData({ data, yAxisUnit: 'short' });
|
||||
expect(short.headers[short.headers.length - 1]).toBe('value');
|
||||
|
||||
const none = exportTimeseriesData({ data, yAxisUnit: 'none' });
|
||||
expect(none.headers[none.headers.length - 1]).toBe('value');
|
||||
});
|
||||
|
||||
it('multi-query: query is its own column; label keys are unioned', () => {
|
||||
const data = [
|
||||
makeQuery('A', [{ series: [makeSeries({ service: 'x' }, [[1000, 1]])] }]),
|
||||
|
||||
@@ -98,9 +98,16 @@ function flatten(
|
||||
return flat;
|
||||
}
|
||||
|
||||
// Display-format ids, not physical units — meaningful on a chart axis
|
||||
// (compact-number formatting) but misleading in an export header.
|
||||
const DISPLAY_ONLY_UNITS = new Set(['short', 'none']);
|
||||
|
||||
// Appends the y-axis unit to the value header: `value` → `value (ms)`.
|
||||
function withUnit(header: string, yAxisUnit?: string): string {
|
||||
return yAxisUnit ? `${header} (${yAxisUnit})` : header;
|
||||
if (!yAxisUnit || DISPLAY_ONLY_UNITS.has(yAxisUnit)) {
|
||||
return header;
|
||||
}
|
||||
return `${header} (${yAxisUnit})`;
|
||||
}
|
||||
|
||||
function toIso(timestamp: number): string {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useSelector } from 'react-redux';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters';
|
||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import { convertDataValueToMs } from 'container/TimeSeriesView/utils';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
@@ -116,9 +115,6 @@ function TimeSeriesViewContainer({
|
||||
|
||||
return (
|
||||
<div className="trace-explorer-time-series-view-container">
|
||||
<div className="trace-explorer-time-series-view-container-header">
|
||||
<BuilderUnitsFilter onChange={onUnitChange} yAxisUnit={yAxisUnit} />
|
||||
</div>
|
||||
<TimeSeriesView
|
||||
isFilterApplied={isFilterApplied}
|
||||
isError={isError}
|
||||
@@ -126,8 +122,10 @@ function TimeSeriesViewContainer({
|
||||
isLoading={isLoading || isFetching}
|
||||
data={responseData}
|
||||
yAxisUnit={yAxisUnit}
|
||||
onYAxisUnitChange={onUnitChange}
|
||||
dataSource={dataSource}
|
||||
setWarning={setWarning}
|
||||
allowExport
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,11 @@ import {
|
||||
IClickHouseQuery,
|
||||
IPromQLQuery,
|
||||
} from '../queryBuilder/queryBuilderData';
|
||||
import { ExecStats, QueryRangeRequestV5 } from '../v5/queryRange';
|
||||
import {
|
||||
ExecStats,
|
||||
QueryRangeRequestV5,
|
||||
QueryRangeResponseV5,
|
||||
} from '../v5/queryRange';
|
||||
import { QueryData, QueryDataV3 } from '../widgets/getQuery';
|
||||
|
||||
export type QueryRangePayload = {
|
||||
@@ -48,6 +52,9 @@ export interface MetricQueryRangeSuccessResponse extends SuccessResponse<
|
||||
> {
|
||||
warning?: Warning;
|
||||
meta?: ExecStats;
|
||||
// Raw V5 response (pre-legacy-conversion) + per-query legend map, for client-side export.
|
||||
rawV5Response?: QueryRangeResponseV5;
|
||||
legendMap?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface MetricRangePayloadV3 {
|
||||
|
||||
@@ -21,6 +21,7 @@ func buildClusterRecords(
|
||||
nodeConditionCountsMap map[string]nodeConditionCounts,
|
||||
podPhaseCountsMap map[string]podPhaseCounts,
|
||||
podStatusCounts map[string]podStatusCounts,
|
||||
resourceCounts map[string]map[string]int64,
|
||||
) []inframonitoringtypes.ClusterRecord {
|
||||
metricsMap := parseFullQueryResponse(resp, groupBy)
|
||||
|
||||
@@ -74,6 +75,15 @@ func buildClusterRecords(
|
||||
record.PodCountsByStatus = podStatusCountsToResponse(podStatusCountsForGroup)
|
||||
}
|
||||
|
||||
if counts, ok := resourceCounts[compositeKey]; ok {
|
||||
record.Counts.Nodes = counts[inframonitoringtypes.NodeNameAttrKey]
|
||||
record.Counts.Namespaces = counts[inframonitoringtypes.NamespaceNameAttrKey]
|
||||
record.Counts.Deployments = counts[inframonitoringtypes.DeploymentNameAttrKey]
|
||||
record.Counts.DaemonSets = counts[inframonitoringtypes.DaemonSetNameAttrKey]
|
||||
record.Counts.Jobs = counts[inframonitoringtypes.JobNameAttrKey]
|
||||
record.Counts.StatefulSets = counts[inframonitoringtypes.StatefulSetNameAttrKey]
|
||||
}
|
||||
|
||||
if attrs, ok := metadataMap[compositeKey]; ok {
|
||||
for k, v := range attrs {
|
||||
record.Meta[k] = v
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package implinframonitoring
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
@@ -35,6 +37,26 @@ var clusterAttrKeysForMetadata = []string{
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
// clusterCountAttrKeys are the resource attributes whose distinct values are
|
||||
// counted per cluster. Node name is read from the node metric universe, while
|
||||
// namespace + workload names come from the pod metric universe — both unioned
|
||||
// into clusterCountMetricNamesList.
|
||||
var clusterCountAttrKeys = []string{
|
||||
inframonitoringtypes.NodeNameAttrKey,
|
||||
inframonitoringtypes.NamespaceNameAttrKey,
|
||||
inframonitoringtypes.DeploymentNameAttrKey,
|
||||
inframonitoringtypes.DaemonSetNameAttrKey,
|
||||
inframonitoringtypes.JobNameAttrKey,
|
||||
inframonitoringtypes.StatefulSetNameAttrKey,
|
||||
}
|
||||
|
||||
// clusterMetricNamesListForCounts is the metric universe for per-cluster distinct
|
||||
// counts. It unions the pod universe (carries namespace + workload owner labels)
|
||||
// with the cluster/node universe (carries k8s.node.name), so a single query can
|
||||
// count nodes, namespaces, and workloads per cluster. Overlapping pod
|
||||
// phase/status metrics are left in — harmless in a metric_name IN (...) list.
|
||||
var clusterMetricNamesListForCounts = slices.Concat(podsTableMetricNamesList, clustersTableMetricNamesList)
|
||||
|
||||
var orderByToClustersQueryNames = map[string][]string{
|
||||
inframonitoringtypes.ClustersOrderByCPU: {"A"},
|
||||
inframonitoringtypes.ClustersOrderByCPUAllocatable: {"B"},
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
@@ -762,3 +763,197 @@ func (m *module) getMetadata(
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// countAttrIdentityTuples maps a counted attr to the full label tuple that
|
||||
// uniquely identifies its entity, so uniqExact counts real entities rather than
|
||||
// bare names (namespace-scoped workloads with the same name across namespaces,
|
||||
// or cluster-scoped nodes/namespaces across clusters, would otherwise collapse).
|
||||
// Attrs absent from this map are counted on their bare name.
|
||||
var countAttrIdentityTuples = map[string][]string{
|
||||
inframonitoringtypes.NamespaceNameAttrKey: {inframonitoringtypes.ClusterNameAttrKey, inframonitoringtypes.NamespaceNameAttrKey},
|
||||
inframonitoringtypes.NodeNameAttrKey: {inframonitoringtypes.ClusterNameAttrKey, inframonitoringtypes.NodeNameAttrKey},
|
||||
inframonitoringtypes.DeploymentNameAttrKey: {inframonitoringtypes.ClusterNameAttrKey, inframonitoringtypes.NamespaceNameAttrKey, inframonitoringtypes.DeploymentNameAttrKey},
|
||||
inframonitoringtypes.DaemonSetNameAttrKey: {inframonitoringtypes.ClusterNameAttrKey, inframonitoringtypes.NamespaceNameAttrKey, inframonitoringtypes.DaemonSetNameAttrKey},
|
||||
inframonitoringtypes.JobNameAttrKey: {inframonitoringtypes.ClusterNameAttrKey, inframonitoringtypes.NamespaceNameAttrKey, inframonitoringtypes.JobNameAttrKey},
|
||||
inframonitoringtypes.StatefulSetNameAttrKey: {inframonitoringtypes.ClusterNameAttrKey, inframonitoringtypes.NamespaceNameAttrKey, inframonitoringtypes.StatefulSetNameAttrKey},
|
||||
}
|
||||
|
||||
// getPerGroupDistinctCounts returns, per groupBy combination, the exact distinct
|
||||
// count of each attr in attrNames within the time range and metric universe.
|
||||
// It mirrors getMetadata: fingerprints come from the samples table, labels are
|
||||
// read from the timeseries table (raw only, or raw+reduced union when reduction
|
||||
// is enabled), and the user filter is merged with the page-groups IN clauses.
|
||||
// The returned map keys group column values by "\x00", mapping to attr -> count.
|
||||
func (m *module) getPerGroupDistinctCounts(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
start, end int64,
|
||||
filter *qbtypes.Filter,
|
||||
groupBy []qbtypes.GroupByKey,
|
||||
pageGroups []map[string]string,
|
||||
attrNames []string,
|
||||
metricNames []string,
|
||||
) (map[string]map[string]int64, error) {
|
||||
if len(pageGroups) == 0 || len(groupBy) == 0 {
|
||||
return map[string]map[string]int64{}, nil
|
||||
}
|
||||
if len(attrNames) == 0 {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "attrNames must not be empty")
|
||||
}
|
||||
if len(metricNames) == 0 {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "metricNames must not be empty")
|
||||
}
|
||||
|
||||
// Merge user filter with page-groups IN clauses.
|
||||
userFilterExpr := ""
|
||||
if filter != nil {
|
||||
userFilterExpr = filter.Expression
|
||||
}
|
||||
pageGroupsFilterExpr := buildPageGroupsFilterExpr(pageGroups)
|
||||
mergedFilterExpr := mergeFilterExpressions(userFilterExpr, pageGroupsFilterExpr)
|
||||
|
||||
reductionEnabled := m.fl.BooleanOrEmpty(ctx, flagger.FeatureEnableMetricsReduction, featuretypes.NewFlaggerEvaluationContext(orgID))
|
||||
|
||||
// Step-floor the window and pick the right tables — same bounds the QB v5
|
||||
// metric querier uses (see alignedMetricWindow / getMetadata).
|
||||
samplesStartMs, flooredEndMs, tsAdjustedStartMs, distributedTimeSeriesTbl, _, _, localSamplesTbl := alignedMetricWindow(start, end)
|
||||
|
||||
fpSB := m.buildSamplesTblFingerprintSubQuery(metricNames, localSamplesTbl, samplesStartMs, flooredEndMs)
|
||||
|
||||
groupByCols := make([]string, len(groupBy))
|
||||
for i, key := range groupBy {
|
||||
groupByCols[i] = key.Name
|
||||
}
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
|
||||
// SELECT: one JSONExtractString per groupBy col + one uniqExactIf per attr.
|
||||
selectCols := make([]string, 0, len(groupByCols)+len(attrNames))
|
||||
for _, col := range groupByCols {
|
||||
selectCols = append(selectCols,
|
||||
fmt.Sprintf("JSONExtractString(labels, %s) AS %s", sb.Var(col), quoteIdentifier(col)),
|
||||
)
|
||||
}
|
||||
for _, attr := range attrNames {
|
||||
// Guard on != '' so a series missing the attr isn't counted as one empty value.
|
||||
extract := fmt.Sprintf("JSONExtractString(labels, %s)", sb.Var(attr))
|
||||
|
||||
// Count on the entity's full identity tuple where one is defined, so
|
||||
// same-named entities in different scopes (e.g. workloads sharing a name
|
||||
// across namespaces) aren't collapsed. Falls back to the bare name.
|
||||
valueExpr := extract
|
||||
if tuple, ok := countAttrIdentityTuples[attr]; ok {
|
||||
parts := make([]string, len(tuple))
|
||||
for i, col := range tuple {
|
||||
parts[i] = fmt.Sprintf("JSONExtractString(labels, %s)", sb.Var(col))
|
||||
}
|
||||
valueExpr = fmt.Sprintf("(%s)", strings.Join(parts, ", "))
|
||||
}
|
||||
|
||||
selectCols = append(selectCols,
|
||||
fmt.Sprintf("uniqExactIf(%s, %s != '') AS %s", valueExpr, extract, quoteIdentifier(attr)),
|
||||
)
|
||||
}
|
||||
sb.Select(selectCols...)
|
||||
|
||||
if reductionEnabled {
|
||||
var filterClause *sqlbuilder.WhereClause
|
||||
if mergedFilterExpr != "" {
|
||||
var err error
|
||||
filterClause, err = m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
reducedFpSB := m.buildReducedSamplesTblFingerprintSubQuery(metricNames, samplesStartMs, flooredEndMs)
|
||||
|
||||
rawSrc := sqlbuilder.NewSelectBuilder()
|
||||
rawSrc.Select("labels")
|
||||
rawSrc.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedTimeSeriesTbl))
|
||||
rawSrc.Where(
|
||||
rawSrc.In("metric_name", sqlbuilder.List(metricNames)),
|
||||
rawSrc.GE("unix_milli", tsAdjustedStartMs),
|
||||
rawSrc.LE("unix_milli", flooredEndMs),
|
||||
fmt.Sprintf("fingerprint IN (%s)", rawSrc.Var(fpSB)),
|
||||
)
|
||||
if filterClause != nil {
|
||||
rawSrc.AddWhereClause(sqlbuilder.CopyWhereClause(filterClause))
|
||||
}
|
||||
|
||||
reducedSrc := sqlbuilder.NewSelectBuilder()
|
||||
reducedSrc.Select("labels")
|
||||
reducedSrc.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, telemetrymetrics.TimeseriesV4ReducedTableName))
|
||||
reducedSrc.Where(
|
||||
reducedSrc.In("metric_name", sqlbuilder.List(metricNames)),
|
||||
reducedSrc.GE("unix_milli", tsAdjustedStartMs),
|
||||
reducedSrc.LE("unix_milli", flooredEndMs),
|
||||
fmt.Sprintf("fingerprint IN (%s)", reducedSrc.Var(reducedFpSB)),
|
||||
)
|
||||
if filterClause != nil {
|
||||
reducedSrc.AddWhereClause(sqlbuilder.CopyWhereClause(filterClause))
|
||||
}
|
||||
|
||||
sb.From(sb.BuilderAs(sqlbuilder.UnionAll(rawSrc, reducedSrc), "series"))
|
||||
} else {
|
||||
sb.From(fmt.Sprintf("%s.%s", telemetrymetrics.DBName, distributedTimeSeriesTbl))
|
||||
sb.Where(
|
||||
sb.In("metric_name", sqlbuilder.List(metricNames)),
|
||||
sb.GE("unix_milli", tsAdjustedStartMs),
|
||||
sb.LE("unix_milli", flooredEndMs),
|
||||
fmt.Sprintf("fingerprint IN (%s)", sb.Var(fpSB)),
|
||||
)
|
||||
if mergedFilterExpr != "" {
|
||||
filterClause, err := m.buildFilterClause(ctx, &qbtypes.Filter{Expression: mergedFilterExpr}, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filterClause != nil {
|
||||
sb.AddWhereClause(filterClause)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupByAliases := make([]string, 0, len(groupByCols))
|
||||
for _, col := range groupByCols {
|
||||
groupByAliases = append(groupByAliases, quoteIdentifier(col))
|
||||
}
|
||||
sb.GroupBy(groupByAliases...)
|
||||
|
||||
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
|
||||
rows, err := m.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
result := make(map[string]map[string]int64)
|
||||
for rows.Next() {
|
||||
groupVals := make([]string, len(groupByCols))
|
||||
counts := make([]uint64, len(attrNames))
|
||||
|
||||
scanPtrs := make([]any, 0, len(groupByCols)+len(attrNames))
|
||||
for i := range groupVals {
|
||||
scanPtrs = append(scanPtrs, &groupVals[i])
|
||||
}
|
||||
for i := range counts {
|
||||
scanPtrs = append(scanPtrs, &counts[i])
|
||||
}
|
||||
|
||||
if err := rows.Scan(scanPtrs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrCounts := make(map[string]int64, len(attrNames))
|
||||
for i, attr := range attrNames {
|
||||
attrCounts[attr] = int64(counts[i])
|
||||
}
|
||||
result[compositeKeyFromList(groupVals)] = attrCounts
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -632,6 +632,7 @@ func (m *module) ListNamespaces(ctx context.Context, orgID valuer.UUID, req *inf
|
||||
phaseCounts map[string]podPhaseCounts
|
||||
podStatusCounts map[string]podStatusCounts
|
||||
podStatusWarning *qbtypes.QueryWarnData
|
||||
resourceCounts map[string]map[string]int64
|
||||
)
|
||||
|
||||
g, gCtx := errgroup.WithContext(ctx)
|
||||
@@ -651,12 +652,17 @@ func (m *module) ListNamespaces(ctx context.Context, orgID valuer.UUID, req *inf
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
resourceCounts, err = m.getPerGroupDistinctCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups, namespaceCountAttrKeys, namespacesTableMetricNamesList)
|
||||
return err
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Records = buildNamespaceRecords(queryResp, pageGroups, req.GroupBy, metadataMap, phaseCounts, podStatusCounts)
|
||||
resp.Records = buildNamespaceRecords(queryResp, pageGroups, req.GroupBy, metadataMap, phaseCounts, podStatusCounts, resourceCounts)
|
||||
resp.Warning = mergeQueryWarnings(queryResp.Warning, podStatusWarning)
|
||||
|
||||
return resp, nil
|
||||
@@ -732,6 +738,7 @@ func (m *module) ListClusters(ctx context.Context, orgID valuer.UUID, req *infra
|
||||
podPhaseCountsMap map[string]podPhaseCounts
|
||||
podStatusCounts map[string]podStatusCounts
|
||||
podStatusWarning *qbtypes.QueryWarnData
|
||||
resourceCounts map[string]map[string]int64
|
||||
)
|
||||
|
||||
g, gCtx := errgroup.WithContext(ctx)
|
||||
@@ -756,12 +763,17 @@ func (m *module) ListClusters(ctx context.Context, orgID valuer.UUID, req *infra
|
||||
podStatusCounts, podStatusWarning, err = m.getPerGroupPodStatusCountsWithReqMetricChecks(gCtx, req.Start, req.End, req.Filter, req.GroupBy, pageGroups)
|
||||
return err
|
||||
})
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
resourceCounts, err = m.getPerGroupDistinctCounts(gCtx, orgID, req.Start, req.End, req.Filter, req.GroupBy, pageGroups, clusterCountAttrKeys, clusterMetricNamesListForCounts)
|
||||
return err
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Records = buildClusterRecords(queryResp, pageGroups, req.GroupBy, metadataMap, nodeConditionCountsMap, podPhaseCountsMap, podStatusCounts)
|
||||
resp.Records = buildClusterRecords(queryResp, pageGroups, req.GroupBy, metadataMap, nodeConditionCountsMap, podPhaseCountsMap, podStatusCounts, resourceCounts)
|
||||
resp.Warning = mergeQueryWarnings(queryResp.Warning, podStatusWarning)
|
||||
|
||||
return resp, nil
|
||||
|
||||
@@ -19,6 +19,7 @@ func buildNamespaceRecords(
|
||||
metadataMap map[string]map[string]string,
|
||||
phaseCounts map[string]podPhaseCounts,
|
||||
podStatusCounts map[string]podStatusCounts,
|
||||
resourceCounts map[string]map[string]int64,
|
||||
) []inframonitoringtypes.NamespaceRecord {
|
||||
metricsMap := parseFullQueryResponse(resp, groupBy)
|
||||
|
||||
@@ -57,6 +58,13 @@ func buildNamespaceRecords(
|
||||
record.PodCountsByStatus = podStatusCountsToResponse(podStatusCountsForGroup)
|
||||
}
|
||||
|
||||
if counts, ok := resourceCounts[compositeKey]; ok {
|
||||
record.Counts.Deployments = counts[inframonitoringtypes.DeploymentNameAttrKey]
|
||||
record.Counts.DaemonSets = counts[inframonitoringtypes.DaemonSetNameAttrKey]
|
||||
record.Counts.Jobs = counts[inframonitoringtypes.JobNameAttrKey]
|
||||
record.Counts.StatefulSets = counts[inframonitoringtypes.StatefulSetNameAttrKey]
|
||||
}
|
||||
|
||||
if attrs, ok := metadataMap[compositeKey]; ok {
|
||||
for k, v := range attrs {
|
||||
record.Meta[k] = v
|
||||
|
||||
@@ -32,6 +32,16 @@ var namespaceAttrKeysForMetadata = []string{
|
||||
"k8s.cluster.name",
|
||||
}
|
||||
|
||||
// namespaceCountAttrKeys are the workload resource attributes whose distinct
|
||||
// values are counted per namespace. They are read from the pod metric universe,
|
||||
// which carries the owner workload names for each pod series.
|
||||
var namespaceCountAttrKeys = []string{
|
||||
inframonitoringtypes.DeploymentNameAttrKey,
|
||||
inframonitoringtypes.DaemonSetNameAttrKey,
|
||||
inframonitoringtypes.JobNameAttrKey,
|
||||
inframonitoringtypes.StatefulSetNameAttrKey,
|
||||
}
|
||||
|
||||
var orderByToNamespacesQueryNames = map[string][]string{
|
||||
inframonitoringtypes.NamespacesOrderByCPU: {"A"},
|
||||
inframonitoringtypes.NamespacesOrderByMemory: {"D"},
|
||||
|
||||
@@ -28,7 +28,15 @@ type ClusterRecord struct {
|
||||
NodeCountsByReadiness NodeCountsByReadiness `json:"nodeCountsByReadiness" required:"true"`
|
||||
PodCountsByPhase PodCountsByPhase `json:"podCountsByPhase" required:"true"`
|
||||
PodCountsByStatus PodCountsByStatus `json:"podCountsByStatus" required:"true"`
|
||||
Meta map[string]string `json:"meta" required:"true"`
|
||||
Counts struct {
|
||||
Nodes int64 `json:"nodes" required:"true"`
|
||||
Namespaces int64 `json:"namespaces" required:"true"`
|
||||
Deployments int64 `json:"deployments" required:"true"`
|
||||
DaemonSets int64 `json:"daemonSets" required:"true"`
|
||||
Jobs int64 `json:"jobs" required:"true"`
|
||||
StatefulSets int64 `json:"statefulSets" required:"true"`
|
||||
} `json:"counts" required:"true"`
|
||||
Meta map[string]string `json:"meta" required:"true"`
|
||||
}
|
||||
|
||||
// PostableClusters is the request body for the v2 clusters list API.
|
||||
|
||||
@@ -22,7 +22,13 @@ type NamespaceRecord struct {
|
||||
NamespaceMemory float64 `json:"namespaceMemory" required:"true"`
|
||||
PodCountsByPhase PodCountsByPhase `json:"podCountsByPhase" required:"true"`
|
||||
PodCountsByStatus PodCountsByStatus `json:"podCountsByStatus" required:"true"`
|
||||
Meta map[string]string `json:"meta" required:"true"`
|
||||
Counts struct {
|
||||
Deployments int64 `json:"deployments" required:"true"`
|
||||
DaemonSets int64 `json:"daemonSets" required:"true"`
|
||||
Jobs int64 `json:"jobs" required:"true"`
|
||||
StatefulSets int64 `json:"statefulSets" required:"true"`
|
||||
} `json:"counts" required:"true"`
|
||||
Meta map[string]string `json:"meta" required:"true"`
|
||||
}
|
||||
|
||||
// PostableNamespaces is the request body for the v2 namespaces list API.
|
||||
|
||||
@@ -100,7 +100,7 @@ func (QueryType) Enum() []any {
|
||||
}
|
||||
|
||||
type AlertCompositeQuery struct {
|
||||
Queries []qbtypes.QueryEnvelope `json:"queries" required:"true"`
|
||||
Queries []qbtypes.QueryEnvelope `json:"queries" required:"true" minItems:"1"`
|
||||
|
||||
PanelType PanelType `json:"panelType" required:"true"`
|
||||
QueryType QueryType `json:"queryType" required:"true"`
|
||||
|
||||
@@ -76,9 +76,9 @@ type PostableRule struct {
|
||||
}
|
||||
|
||||
type NotificationSettings struct {
|
||||
GroupBy []string `json:"groupBy,omitempty"`
|
||||
Renotify Renotify `json:"renotify,omitzero"`
|
||||
UsePolicy bool `json:"usePolicy,omitempty"`
|
||||
GroupBy []string `json:"groupBy,omitempty"`
|
||||
Renotify *Renotify `json:"renotify,omitempty"`
|
||||
UsePolicy bool `json:"usePolicy,omitempty"`
|
||||
// NewGroupEvalDelay is the grace period for new series to be excluded from alerts evaluation
|
||||
NewGroupEvalDelay valuer.TextDuration `json:"newGroupEvalDelay,omitzero"`
|
||||
}
|
||||
@@ -92,7 +92,7 @@ type Renotify struct {
|
||||
func (ns *NotificationSettings) GetAlertManagerNotificationConfig() alertmanagertypes.NotificationConfig {
|
||||
var renotifyInterval time.Duration
|
||||
var noDataRenotifyInterval time.Duration
|
||||
if ns.Renotify.Enabled {
|
||||
if ns.Renotify != nil && ns.Renotify.Enabled {
|
||||
if slices.Contains(ns.Renotify.AlertStates, StateNoData) {
|
||||
noDataRenotifyInterval = ns.Renotify.ReNotifyInterval.Duration()
|
||||
}
|
||||
@@ -204,10 +204,12 @@ func (ns *NotificationSettings) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
// Validate states after unmarshaling
|
||||
for _, state := range ns.Renotify.AlertStates {
|
||||
if state != StateFiring && state != StateNoData {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid alert state: %s", state)
|
||||
if ns.Renotify != nil {
|
||||
for _, state := range ns.Renotify.AlertStates {
|
||||
if state != StateFiring && state != StateNoData {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid alert state: %s", state)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -220,6 +222,11 @@ func (r *PostableRule) processRuleDefaults() {
|
||||
r.SchemaVersion = DefaultSchemaVersion
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): remove as this is now a legacy field
|
||||
if r.Version == "" {
|
||||
r.Version = "v5"
|
||||
}
|
||||
|
||||
// v2alpha1 uses the Evaluation envelope for window/frequency;
|
||||
// only default top-level fields for v1.
|
||||
if r.SchemaVersion != SchemaVersionV2Alpha1 {
|
||||
@@ -271,7 +278,7 @@ func (r *PostableRule) processRuleDefaults() {
|
||||
r.RuleCondition.Thresholds = &thresholdData
|
||||
r.Evaluation = &EvaluationEnvelope{RollingEvaluation, RollingWindow{EvalWindow: r.EvalWindow, Frequency: r.Frequency}}
|
||||
r.NotificationSettings = &NotificationSettings{
|
||||
Renotify: Renotify{
|
||||
Renotify: &Renotify{
|
||||
Enabled: true,
|
||||
ReNotifyInterval: valuer.MustParseTextDuration("4h"),
|
||||
AlertStates: []AlertState{StateFiring},
|
||||
@@ -557,7 +564,7 @@ func (r *PostableRule) validateV2Alpha1() []error {
|
||||
errs = append(errs, errors.NewInvalidInputf(errors.CodeInvalidInput,
|
||||
"notificationSettings: field is required for schemaVersion %q", SchemaVersionV2Alpha1))
|
||||
} else {
|
||||
if r.NotificationSettings.Renotify.Enabled && !r.NotificationSettings.Renotify.ReNotifyInterval.IsPositive() {
|
||||
if r.NotificationSettings.Renotify != nil && r.NotificationSettings.Renotify.Enabled && !r.NotificationSettings.Renotify.ReNotifyInterval.IsPositive() {
|
||||
errs = append(errs, errors.NewInvalidInputf(errors.CodeInvalidInput,
|
||||
"notificationSettings.renotify.interval: must be a positive duration when renotify is enabled"))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
@@ -1327,3 +1328,27 @@ func TestAnomalyNegationEval(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionDefaultsToV5(t *testing.T) {
|
||||
content := `{
|
||||
"alert": "cpu high",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
"ruleType": "threshold_rule",
|
||||
"schemaVersion": "v2alpha1",
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queries": [{"type": "promql", "spec": {"name": "A", "query": "up"}}],
|
||||
"panelType": "graph",
|
||||
"queryType": "promql"
|
||||
},
|
||||
"thresholds": {"kind": "basic", "spec": [{"name": "critical", "target": 90, "matchType": "1", "op": "1"}]}
|
||||
},
|
||||
"evaluation": {"kind": "rolling", "spec": {"evalWindow": "5m", "frequency": "1m"}},
|
||||
"notificationSettings": {"usePolicy": false}
|
||||
}`
|
||||
|
||||
rule := PostableRule{}
|
||||
require.NoError(t, json.Unmarshal([]byte(content), &rule))
|
||||
assert.Equal(t, "v5", rule.Version)
|
||||
assert.NoError(t, rule.Validate())
|
||||
}
|
||||
|
||||
46
pkg/types/ruletypes/jsonschema.go
Normal file
46
pkg/types/ruletypes/jsonschema.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package ruletypes
|
||||
|
||||
import (
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
)
|
||||
|
||||
var (
|
||||
_ jsonschema.Preparer = (*PostableRule)(nil)
|
||||
_ jsonschema.Preparer = (*RuleCondition)(nil)
|
||||
)
|
||||
|
||||
// PrepareJSONSchema restricts the published rule schema to the v2alpha1
|
||||
// contract: the v1-only fields (evalWindow, frequency, preferredChannels) and
|
||||
// the query version (always "v5", defaulted by the server) are omitted, and
|
||||
// the fields the server requires for schemaVersion v2alpha1 are marked
|
||||
// required. Runtime JSON handling is unchanged and continues to load stored
|
||||
// v1 rules until schemaVersion v1 is removed.
|
||||
//
|
||||
// Rule and GettableRule embed PostableRule, so this also applies to the
|
||||
// published read models through method promotion.
|
||||
func (r *PostableRule) PrepareJSONSchema(schema *jsonschema.Schema) error {
|
||||
for _, name := range []string{"evalWindow", "frequency", "preferredChannels", "version", "source"} {
|
||||
delete(schema.Properties, name)
|
||||
}
|
||||
|
||||
schema.Required = append(schema.Required, "schemaVersion", "evaluation", "notificationSettings")
|
||||
|
||||
if prop, ok := schema.Properties["schemaVersion"]; ok && prop.TypeObject != nil {
|
||||
prop.TypeObject.WithEnum(SchemaVersionV2Alpha1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrepareJSONSchema removes the v1-only condition fields (target, op,
|
||||
// matchType, targetUnit — expressed per threshold entry in v2alpha1) from the
|
||||
// published schema and marks thresholds and selectedQueryName required.
|
||||
func (rc *RuleCondition) PrepareJSONSchema(schema *jsonschema.Schema) error {
|
||||
for _, name := range []string{"target", "op", "matchType", "targetUnit"} {
|
||||
delete(schema.Properties, name)
|
||||
}
|
||||
|
||||
schema.Required = append(schema.Required, "thresholds", "selectedQueryName")
|
||||
|
||||
return nil
|
||||
}
|
||||
258
pkg/types/ruletypes/roundtrip_test.go
Normal file
258
pkg/types/ruletypes/roundtrip_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package ruletypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func readBody(t *testing.T, stored string) map[string]json.RawMessage {
|
||||
t.Helper()
|
||||
g := GettableRule{}
|
||||
require.NoError(t, json.Unmarshal([]byte(stored), &g))
|
||||
out, err := json.Marshal(NewRule(&g))
|
||||
require.NoError(t, err)
|
||||
var body map[string]json.RawMessage
|
||||
require.NoError(t, json.Unmarshal(out, &body))
|
||||
return body
|
||||
}
|
||||
|
||||
func TestV2RoundTripRolling(t *testing.T) {
|
||||
thresholds := `{
|
||||
"kind": "basic",
|
||||
"spec": [
|
||||
{"name": "critical", "target": 90.5, "targetUnit": "%", "recoveryTarget": 80.5, "matchType": "at_least_once", "op": "above", "channels": ["slack-critical"]},
|
||||
{"name": "warning", "target": 75, "targetUnit": "%", "matchType": "at_least_once", "op": "above", "channels": ["slack-warnings", "email-oncall"]}
|
||||
]
|
||||
}`
|
||||
thresholdsEchoed := `{
|
||||
"kind": "basic",
|
||||
"spec": [
|
||||
{"name": "critical", "target": 90.5, "targetUnit": "%", "recoveryTarget": 80.5, "matchType": "at_least_once", "op": "above", "channels": ["slack-critical"]},
|
||||
{"name": "warning", "target": 75, "targetUnit": "%", "recoveryTarget": null, "matchType": "at_least_once", "op": "above", "channels": ["slack-warnings", "email-oncall"]}
|
||||
]
|
||||
}`
|
||||
evaluation := `{"kind": "rolling", "spec": {"evalWindow": "90m", "frequency": "90s"}}`
|
||||
notificationSettings := `{
|
||||
"groupBy": ["service.name", "deployment.environment"],
|
||||
"renotify": {"enabled": true, "interval": "45m", "alertStates": ["firing", "nodata"]},
|
||||
"usePolicy": true,
|
||||
"newGroupEvalDelay": "10m"
|
||||
}`
|
||||
labels := `{"team": "infra", "severity": "critical"}`
|
||||
annotations := `{"summary": "CPU above {{$threshold}}", "description": "value {{$value}}"}`
|
||||
|
||||
stored := `{
|
||||
"alert": "cpu high",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
"description": "watches cpu",
|
||||
"ruleType": "promql_rule",
|
||||
"schemaVersion": "v2alpha1",
|
||||
"version": "v5",
|
||||
"disabled": true,
|
||||
"labels": ` + labels + `,
|
||||
"annotations": ` + annotations + `,
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queries": [{"type": "promql", "spec": {"name": "A", "query": "avg(cpu_usage)"}}],
|
||||
"panelType": "graph",
|
||||
"queryType": "promql",
|
||||
"unit": "percent"
|
||||
},
|
||||
"selectedQueryName": "A",
|
||||
"alertOnAbsent": true,
|
||||
"absentFor": 10,
|
||||
"requireMinPoints": true,
|
||||
"requiredNumPoints": 4,
|
||||
"thresholds": ` + thresholds + `
|
||||
},
|
||||
"evaluation": ` + evaluation + `,
|
||||
"notificationSettings": ` + notificationSettings + `
|
||||
}`
|
||||
|
||||
body := readBody(t, stored)
|
||||
|
||||
assert.JSONEq(t, `"cpu high"`, string(body["alert"]))
|
||||
assert.JSONEq(t, `"METRIC_BASED_ALERT"`, string(body["alertType"]))
|
||||
assert.JSONEq(t, `"watches cpu"`, string(body["description"]))
|
||||
assert.JSONEq(t, `"promql_rule"`, string(body["ruleType"]))
|
||||
assert.JSONEq(t, `"v2alpha1"`, string(body["schemaVersion"]))
|
||||
assert.JSONEq(t, `true`, string(body["disabled"]))
|
||||
assert.JSONEq(t, labels, string(body["labels"]))
|
||||
assert.JSONEq(t, annotations, string(body["annotations"]))
|
||||
assert.JSONEq(t, evaluation, string(body["evaluation"]))
|
||||
assert.JSONEq(t, notificationSettings, string(body["notificationSettings"]))
|
||||
|
||||
var condition map[string]json.RawMessage
|
||||
require.NoError(t, json.Unmarshal(body["condition"], &condition))
|
||||
assert.JSONEq(t, thresholdsEchoed, string(condition["thresholds"]))
|
||||
assert.JSONEq(t, `"A"`, string(condition["selectedQueryName"]))
|
||||
assert.JSONEq(t, `true`, string(condition["alertOnAbsent"]))
|
||||
assert.JSONEq(t, `10`, string(condition["absentFor"]))
|
||||
assert.JSONEq(t, `true`, string(condition["requireMinPoints"]))
|
||||
assert.JSONEq(t, `4`, string(condition["requiredNumPoints"]))
|
||||
}
|
||||
|
||||
func TestV2RoundTripCumulative(t *testing.T) {
|
||||
evaluation := `{
|
||||
"kind": "cumulative",
|
||||
"spec": {
|
||||
"schedule": {"type": "daily", "minute": 30, "hour": 9},
|
||||
"frequency": "5m",
|
||||
"timezone": "America/New_York"
|
||||
}
|
||||
}`
|
||||
|
||||
stored := `{
|
||||
"alert": "daily budget",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
"ruleType": "threshold_rule",
|
||||
"schemaVersion": "v2alpha1",
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queries": [{"type": "promql", "spec": {"name": "A", "query": "sum(cost)"}}],
|
||||
"panelType": "graph",
|
||||
"queryType": "promql"
|
||||
},
|
||||
"thresholds": {"kind": "basic", "spec": [{"name": "critical", "target": 100, "matchType": "at_least_once", "op": "above"}]}
|
||||
},
|
||||
"evaluation": ` + evaluation + `,
|
||||
"notificationSettings": {"usePolicy": false}
|
||||
}`
|
||||
|
||||
body := readBody(t, stored)
|
||||
assert.JSONEq(t, evaluation, string(body["evaluation"]))
|
||||
}
|
||||
|
||||
func TestV2MinimalReadShape(t *testing.T) {
|
||||
stored := `{
|
||||
"alert": "minimal",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
"ruleType": "threshold_rule",
|
||||
"schemaVersion": "v2alpha1",
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queries": [{"type": "promql", "spec": {"name": "A", "query": "up"}}],
|
||||
"panelType": "graph",
|
||||
"queryType": "promql"
|
||||
},
|
||||
"thresholds": {"kind": "basic", "spec": [{"name": "critical", "target": 90, "matchType": "at_least_once", "op": "above"}]}
|
||||
},
|
||||
"evaluation": {"kind": "rolling", "spec": {"evalWindow": "5m", "frequency": "1m"}},
|
||||
"notificationSettings": {"usePolicy": false}
|
||||
}`
|
||||
|
||||
body := readBody(t, stored)
|
||||
|
||||
for _, field := range []string{"labels", "annotations", "description", "preferredChannels", "evalWindow", "frequency"} {
|
||||
assert.NotContains(t, body, field)
|
||||
}
|
||||
|
||||
var ns map[string]json.RawMessage
|
||||
require.NoError(t, json.Unmarshal(body["notificationSettings"], &ns))
|
||||
for _, field := range []string{"renotify", "groupBy", "newGroupEvalDelay", "usePolicy"} {
|
||||
assert.NotContains(t, ns, field, "notificationSettings.%s", field)
|
||||
}
|
||||
|
||||
assert.JSONEq(t, `false`, string(body["disabled"]))
|
||||
assert.JSONEq(t, `"v5"`, string(body["version"]))
|
||||
|
||||
var condition struct {
|
||||
Thresholds struct {
|
||||
Spec []map[string]json.RawMessage `json:"spec"`
|
||||
} `json:"thresholds"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(body["condition"], &condition))
|
||||
require.Len(t, condition.Thresholds.Spec, 1)
|
||||
spec := condition.Thresholds.Spec[0]
|
||||
assert.JSONEq(t, `""`, string(spec["targetUnit"]))
|
||||
assert.JSONEq(t, `null`, string(spec["channels"]))
|
||||
assert.JSONEq(t, `null`, string(spec["recoveryTarget"]))
|
||||
}
|
||||
|
||||
func TestRenotifyRoundTrip(t *testing.T) {
|
||||
base := `{
|
||||
"alert": "cpu high",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
"ruleType": "threshold_rule",
|
||||
"schemaVersion": "v2alpha1",
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queries": [{"type": "promql", "spec": {"name": "A", "query": "up"}}],
|
||||
"panelType": "graph",
|
||||
"queryType": "promql"
|
||||
},
|
||||
"thresholds": {"kind": "basic", "spec": [{"name": "critical", "target": 90, "matchType": "at_least_once", "op": "above"}]}
|
||||
},
|
||||
"evaluation": {"kind": "rolling", "spec": {"evalWindow": "5m", "frequency": "1m"}},
|
||||
"notificationSettings": %s
|
||||
}`
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
wantRenotify string
|
||||
}{
|
||||
{
|
||||
name: "absent renotify stays absent",
|
||||
settings: `{"usePolicy": false}`,
|
||||
},
|
||||
{
|
||||
name: "explicitly disabled renotify is echoed",
|
||||
settings: `{"renotify": {"enabled": false}}`,
|
||||
wantRenotify: `{"enabled": false}`,
|
||||
},
|
||||
{
|
||||
name: "enabled renotify with states is echoed",
|
||||
settings: `{"renotify": {"enabled": true, "interval": "30m", "alertStates": ["firing"]}}`,
|
||||
wantRenotify: `{"enabled": true, "interval": "30m", "alertStates": ["firing"]}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
body := readBody(t, strings.Replace(base, "%s", tc.settings, 1))
|
||||
var ns map[string]json.RawMessage
|
||||
require.NoError(t, json.Unmarshal(body["notificationSettings"], &ns))
|
||||
if tc.wantRenotify == "" {
|
||||
assert.NotContains(t, ns, "renotify")
|
||||
} else {
|
||||
assert.JSONEq(t, tc.wantRenotify, string(ns["renotify"]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchMergePreservesUnpatchedFields(t *testing.T) {
|
||||
stored := `{
|
||||
"alert": "cpu high",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
"ruleType": "threshold_rule",
|
||||
"schemaVersion": "v2alpha1",
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queries": [{"type": "promql", "spec": {"name": "A", "query": "up"}}],
|
||||
"panelType": "graph",
|
||||
"queryType": "promql"
|
||||
},
|
||||
"thresholds": {"kind": "basic", "spec": [{"name": "critical", "target": 90, "matchType": "at_least_once", "op": "above"}]}
|
||||
},
|
||||
"evaluation": {"kind": "rolling", "spec": {"evalWindow": "5m", "frequency": "1m"}},
|
||||
"notificationSettings": {"renotify": {"enabled": true, "interval": "30m", "alertStates": ["firing"]}}
|
||||
}`
|
||||
|
||||
rule := PostableRule{}
|
||||
require.NoError(t, json.Unmarshal([]byte(stored), &rule))
|
||||
require.NoError(t, json.Unmarshal([]byte(`{"disabled": true}`), &rule))
|
||||
require.NoError(t, rule.Validate())
|
||||
|
||||
assert.True(t, rule.Disabled)
|
||||
assert.NotNil(t, rule.RuleCondition.Thresholds)
|
||||
assert.NotNil(t, rule.Evaluation)
|
||||
require.NotNil(t, rule.NotificationSettings)
|
||||
require.NotNil(t, rule.NotificationSettings.Renotify)
|
||||
assert.True(t, rule.NotificationSettings.Renotify.Enabled)
|
||||
}
|
||||
@@ -169,12 +169,10 @@ func TestValidate_PostableRule_Common(t *testing.T) {
|
||||
errSubstr: "alert",
|
||||
},
|
||||
|
||||
// only "v5" is allowed
|
||||
// only "v5" is allowed; missing/empty defaults to "v5"
|
||||
{
|
||||
name: "missing version",
|
||||
json: removeField(validV1Builder(), "version"),
|
||||
wantErr: true,
|
||||
errSubstr: "version",
|
||||
name: "missing version defaults to v5",
|
||||
json: removeField(validV1Builder(), "version"),
|
||||
},
|
||||
{
|
||||
name: "wrong version v4",
|
||||
@@ -189,10 +187,8 @@ func TestValidate_PostableRule_Common(t *testing.T) {
|
||||
errSubstr: "version",
|
||||
},
|
||||
{
|
||||
name: "empty version",
|
||||
json: patchJSON(validV1Builder(), `{"version": ""}`),
|
||||
wantErr: true,
|
||||
errSubstr: "version",
|
||||
name: "empty version defaults to v5",
|
||||
json: patchJSON(validV1Builder(), `{"version": ""}`),
|
||||
},
|
||||
|
||||
// alert type, capital case to avoid breaking changes
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c1-n1","k8s.node.uid":"acc-c1-n1-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c1-n1","k8s.node.uid":"acc-c1-n1-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c1-n1","k8s.node.uid":"acc-c1-n1-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n1-p-uid","k8s.pod.name":"acc-c1-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n1","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n1-p-uid","k8s.pod.name":"acc-c1-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n1","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n1-p-uid","k8s.pod.name":"acc-c1-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n1","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n1-p-uid","k8s.pod.name":"acc-c1-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n1","k8s.cluster.name":"acc-cluster-1","k8s.deployment.name":"dep-a"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n1-p-uid","k8s.pod.name":"acc-c1-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n1","k8s.cluster.name":"acc-cluster-1","k8s.deployment.name":"dep-a"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n1-p-uid","k8s.pod.name":"acc-c1-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n1","k8s.cluster.name":"acc-cluster-1","k8s.deployment.name":"dep-a"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c1-n2","k8s.node.uid":"acc-c1-n2-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c1-n2","k8s.node.uid":"acc-c1-n2-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c1-n2","k8s.node.uid":"acc-c1-n2-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
@@ -31,9 +31,12 @@
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c1-n2","k8s.node.uid":"acc-c1-n2-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c1-n2","k8s.node.uid":"acc-c1-n2-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c1-n2","k8s.node.uid":"acc-c1-n2-uid","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p-uid","k8s.pod.name":"acc-c1-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p-uid","k8s.pod.name":"acc-c1-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p-uid","k8s.pod.name":"acc-c1-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p-uid","k8s.pod.name":"acc-c1-n2-p","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1","k8s.statefulset.name":"sts-a"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p-uid","k8s.pod.name":"acc-c1-n2-p","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1","k8s.statefulset.name":"sts-a"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p-uid","k8s.pod.name":"acc-c1-n2-p","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1","k8s.statefulset.name":"sts-a"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p2-uid","k8s.pod.name":"acc-c1-n2-p2","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1","k8s.deployment.name":"dep-a"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p2-uid","k8s.pod.name":"acc-c1-n2-p2","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1","k8s.deployment.name":"dep-a"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c1-n2-p2-uid","k8s.pod.name":"acc-c1-n2-p2","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c1-n2","k8s.cluster.name":"acc-cluster-1","k8s.deployment.name":"dep-a"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n1","k8s.node.uid":"acc-c2-n1-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n1","k8s.node.uid":"acc-c2-n1-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n1","k8s.node.uid":"acc-c2-n1-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
@@ -49,9 +52,9 @@
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n1","k8s.node.uid":"acc-c2-n1-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n1","k8s.node.uid":"acc-c2-n1-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n1","k8s.node.uid":"acc-c2-n1-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n1-p-uid","k8s.pod.name":"acc-c2-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n1","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n1-p-uid","k8s.pod.name":"acc-c2-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n1","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n1-p-uid","k8s.pod.name":"acc-c2-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n1","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n1-p-uid","k8s.pod.name":"acc-c2-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n1","k8s.cluster.name":"acc-cluster-2","k8s.deployment.name":"dep-b"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n1-p-uid","k8s.pod.name":"acc-c2-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n1","k8s.cluster.name":"acc-cluster-2","k8s.deployment.name":"dep-b"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n1-p-uid","k8s.pod.name":"acc-c2-n1-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n1","k8s.cluster.name":"acc-cluster-2","k8s.deployment.name":"dep-b"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n2","k8s.node.uid":"acc-c2-n2-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n2","k8s.node.uid":"acc-c2-n2-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n2","k8s.node.uid":"acc-c2-n2-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
@@ -67,9 +70,9 @@
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n2","k8s.node.uid":"acc-c2-n2-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n2","k8s.node.uid":"acc-c2-n2-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n2","k8s.node.uid":"acc-c2-n2-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n2-p-uid","k8s.pod.name":"acc-c2-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n2","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n2-p-uid","k8s.pod.name":"acc-c2-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n2","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n2-p-uid","k8s.pod.name":"acc-c2-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n2","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n2-p-uid","k8s.pod.name":"acc-c2-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n2","k8s.cluster.name":"acc-cluster-2","k8s.daemonset.name":"ds-b"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n2-p-uid","k8s.pod.name":"acc-c2-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n2","k8s.cluster.name":"acc-cluster-2","k8s.daemonset.name":"ds-b"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n2-p-uid","k8s.pod.name":"acc-c2-n2-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n2","k8s.cluster.name":"acc-cluster-2","k8s.daemonset.name":"ds-b"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n3","k8s.node.uid":"acc-c2-n3-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n3","k8s.node.uid":"acc-c2-n3-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.cpu.usage","labels":{"k8s.node.name":"acc-c2-n3","k8s.node.uid":"acc-c2-n3-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":1.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
@@ -85,6 +88,6 @@
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n3","k8s.node.uid":"acc-c2-n3-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n3","k8s.node.uid":"acc-c2-n3-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.node.condition_ready","labels":{"k8s.node.name":"acc-c2-n3","k8s.node.uid":"acc-c2-n3-uid","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":1,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n3-p-uid","k8s.pod.name":"acc-c2-n3-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n3","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n3-p-uid","k8s.pod.name":"acc-c2-n3-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n3","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n3-p-uid","k8s.pod.name":"acc-c2-n3-p","k8s.namespace.name":"ns-x","k8s.node.name":"acc-c2-n3","k8s.cluster.name":"acc-cluster-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n3-p-uid","k8s.pod.name":"acc-c2-n3-p","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c2-n3","k8s.cluster.name":"acc-cluster-2","k8s.job.name":"job-b"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n3-p-uid","k8s.pod.name":"acc-c2-n3-p","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c2-n3","k8s.cluster.name":"acc-cluster-2","k8s.job.name":"job-b"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-c2-n3-p-uid","k8s.pod.name":"acc-c2-n3-p","k8s.namespace.name":"ns-y","k8s.node.name":"acc-c2-n3","k8s.cluster.name":"acc-cluster-2","k8s.job.name":"job-b"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
|
||||
@@ -12,10 +12,18 @@
|
||||
},
|
||||
"podCountsByPhase": {
|
||||
"pending": 0,
|
||||
"running": 2,
|
||||
"running": 3,
|
||||
"succeeded": 0,
|
||||
"failed": 0,
|
||||
"unknown": 0
|
||||
},
|
||||
"counts": {
|
||||
"nodes": 2,
|
||||
"namespaces": 2,
|
||||
"deployments": 2,
|
||||
"daemonSets": 0,
|
||||
"jobs": 0,
|
||||
"statefulSets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34,6 +42,14 @@
|
||||
"succeeded": 0,
|
||||
"failed": 0,
|
||||
"unknown": 0
|
||||
},
|
||||
"counts": {
|
||||
"nodes": 3,
|
||||
"namespaces": 2,
|
||||
"deployments": 1,
|
||||
"daemonSets": 1,
|
||||
"jobs": 1,
|
||||
"statefulSets": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:00:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:02:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:04:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:00:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:02:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:04:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1a-uid","k8s.pod.name":"acc-p1a","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-1"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.5,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":100000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p1b-uid","k8s.pod.name":"acc-p1b","k8s.namespace.name":"acc-ns-1","k8s.cluster.name":"cluster-x","k8s.deployment.name":"web-2"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:00:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:02:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:04:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2a-uid","k8s.pod.name":"acc-p2a","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.deployment.name":"api"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:00:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:02:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:04:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2b-uid","k8s.pod.name":"acc-p2b","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.daemonset.name":"agent"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:00:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:02:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.cpu.usage","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:04:00+00:00","value":0.75,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:00:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:02:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.memory.working_set","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:04:00+00:00","value":200000000.0,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:00:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:02:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
{"metric_name":"k8s.pod.phase","labels":{"k8s.pod.uid":"acc-p2c-uid","k8s.pod.name":"acc-p2c","k8s.namespace.name":"acc-ns-2","k8s.cluster.name":"cluster-y","k8s.job.name":"batch"},"timestamp":"2025-01-10T10:04:00+00:00","value":2,"temporality":"Unspecified","type_":"Gauge","is_monotonic":false}
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
"namespaceName": "acc-ns-1",
|
||||
"namespaceCPU": 1.0,
|
||||
"namespaceMemory": 200000000.0,
|
||||
"podCountsByPhase": {"pending": 0, "running": 2, "succeeded": 0, "failed": 0, "unknown": 0}
|
||||
"podCountsByPhase": {"pending": 0, "running": 2, "succeeded": 0, "failed": 0, "unknown": 0},
|
||||
"counts": {"deployments": 2, "daemonSets": 0, "jobs": 0, "statefulSets": 0}
|
||||
},
|
||||
{
|
||||
"namespaceName": "acc-ns-2",
|
||||
"namespaceCPU": 2.25,
|
||||
"namespaceMemory": 600000000.0,
|
||||
"podCountsByPhase": {"pending": 0, "running": 3, "succeeded": 0, "failed": 0, "unknown": 0}
|
||||
"podCountsByPhase": {"pending": 0, "running": 3, "succeeded": 0, "failed": 0, "unknown": 0},
|
||||
"counts": {"deployments": 1, "daemonSets": 1, "jobs": 1, "statefulSets": 0}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ def test_namespaces_accuracy(
|
||||
"namespaceCPU",
|
||||
"namespaceMemory",
|
||||
"podCountsByPhase",
|
||||
"counts",
|
||||
"meta",
|
||||
):
|
||||
assert field in record, f"missing {field} in {record!r}"
|
||||
@@ -81,6 +82,9 @@ def test_namespaces_accuracy(
|
||||
for bucket in ("pending", "running", "succeeded", "failed", "unknown"):
|
||||
assert bucket in record["podCountsByPhase"]
|
||||
assert isinstance(record["podCountsByPhase"][bucket], int)
|
||||
for bucket in ("deployments", "daemonSets", "jobs", "statefulSets"):
|
||||
assert bucket in record["counts"]
|
||||
assert isinstance(record["counts"][bucket], int)
|
||||
|
||||
assert record["meta"].get("k8s.namespace.name") == record["namespaceName"]
|
||||
assert "k8s.cluster.name" in record["meta"]
|
||||
@@ -90,6 +94,7 @@ def test_namespaces_accuracy(
|
||||
for field in ("namespaceCPU", "namespaceMemory"):
|
||||
assert compare_values(record[field], exp[field], 1e-6), f"{record['namespaceName']}.{field}: got {record[field]}, expected {exp[field]}"
|
||||
assert record["podCountsByPhase"] == exp["podCountsByPhase"]
|
||||
assert record["counts"] == exp["counts"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -78,6 +78,7 @@ def test_clusters_accuracy(
|
||||
"clusterMemoryAllocatable",
|
||||
"nodeCountsByReadiness",
|
||||
"podCountsByPhase",
|
||||
"counts",
|
||||
"meta",
|
||||
):
|
||||
assert field in record, f"missing {field} in {record!r}"
|
||||
@@ -88,6 +89,9 @@ def test_clusters_accuracy(
|
||||
for bucket in ("pending", "running", "succeeded", "failed", "unknown"):
|
||||
assert bucket in record["podCountsByPhase"]
|
||||
assert isinstance(record["podCountsByPhase"][bucket], int)
|
||||
for bucket in ("nodes", "namespaces", "deployments", "daemonSets", "jobs", "statefulSets"):
|
||||
assert bucket in record["counts"]
|
||||
assert isinstance(record["counts"][bucket], int)
|
||||
|
||||
assert record["meta"].get("k8s.cluster.name") == record["clusterName"]
|
||||
|
||||
@@ -102,6 +106,7 @@ def test_clusters_accuracy(
|
||||
assert compare_values(record[field], exp[field], 1e-6), f"{record['clusterName']}.{field}: got {record[field]}, expected {exp[field]}"
|
||||
assert record["nodeCountsByReadiness"] == exp["nodeCountsByReadiness"]
|
||||
assert record["podCountsByPhase"] == exp["podCountsByPhase"]
|
||||
assert record["counts"] == exp["counts"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
Reference in New Issue
Block a user