Skip to content

Commit 7c3110d

Browse files
authored
Add coalesce_time option to get_analytics (#3058)
...that tries to put metric values recorded at similar times (from different cluster nodes) into the same time-bucketed rows so that visualization tools can make more eye-pleasing graphs of them. Defaults to off.
1 parent 4e3ee1d commit 7c3110d

1 file changed

Lines changed: 61 additions & 15 deletions

File tree

resources/analytics/read.ts

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const { forComponent } = harperLogger;
44
import { getAnalyticsHostnameTable } from './hostnames.ts';
55
import type { Condition, Conditions } from '../ResourceInterface.ts';
66
import { METRIC, type BuiltInMetricName } from './metadata.ts';
7+
import { CONFIG_PARAMS } from '../../utility/hdbTerms.ts';
8+
import { get as envGet } from '../../utility/environment/environmentManager.js';
79

810
// default to one week time window for finding custom metrics
911
const defaultCustomMetricWindow = 1000 * 60 * 60 * 24 * 7;
@@ -24,14 +26,21 @@ interface GetAnalyticsRequest {
2426
start_time?: number;
2527
end_time?: number;
2628
get_attributes?: string[];
29+
coalesce_time?: boolean;
2730
conditions?: Conditions;
2831
}
2932

3033
type GetAnalyticsResponse = Metric[];
3134

3235
export function getOp(req: GetAnalyticsRequest): Promise<GetAnalyticsResponse> {
3336
log.trace?.('get_analytics request:', req);
34-
return get(req.metric, req.get_attributes, req.start_time, req.end_time, req.conditions);
37+
return get(req.metric, {
38+
getAttributes: req.get_attributes,
39+
startTime: req.start_time,
40+
endTime: req.end_time,
41+
coalesceTime: req.coalesce_time,
42+
additionalConditions: req.conditions,
43+
});
3544
}
3645

3746
function conformCondition(condition: Condition): Condition {
@@ -48,13 +57,37 @@ function conformCondition(condition: Condition): Condition {
4857
};
4958
}
5059

51-
export async function get(
52-
metric: string,
53-
getAttributes?: string[],
54-
startTime?: number,
55-
endTime?: number,
56-
additionalConditions?: Conditions
57-
): Promise<Metric[]> {
60+
async function coalesceResults(results: Metric[], window: number): Promise<Metric[]> {
61+
const coalescedResults: Metric[] = [];
62+
let coalesceId;
63+
let lastCoalescedId = new Map<string, number>();
64+
for await (const result of results) {
65+
const id = result.id;
66+
if (!coalesceId) {
67+
coalesceId = id;
68+
}
69+
const delta = Math.abs(id - coalesceId);
70+
if (delta < window && lastCoalescedId.get(result.node) !== id) {
71+
coalescedResults.push({ ...result, id: coalesceId });
72+
lastCoalescedId[result.node] = id;
73+
} else {
74+
coalescedResults.push(result);
75+
coalesceId = id;
76+
}
77+
}
78+
return coalescedResults;
79+
}
80+
81+
interface GetAnalyticsOpts {
82+
getAttributes?: string[];
83+
startTime?: number;
84+
endTime?: number;
85+
coalesceTime?: boolean;
86+
additionalConditions?: Conditions;
87+
}
88+
89+
export async function get(metric: string, opts?: GetAnalyticsOpts): Promise<Metric[]> {
90+
const { getAttributes, startTime, endTime, additionalConditions } = opts ?? {};
5891
const conditions: Conditions = [{ attribute: 'metric', comparator: 'equals', value: metric }];
5992
if (additionalConditions) {
6093
conditions.push(...additionalConditions.map(conformCondition));
@@ -88,7 +121,7 @@ export async function get(
88121
log.trace?.('get_analytics hdb_analytics.search request:', JSON.stringify(request));
89122
const searchResults = await databases.system.hdb_analytics.search(request);
90123

91-
return searchResults.map(async (result: Metric) => {
124+
let results = searchResults.map(async (result: Metric) => {
92125
// remove nodeId from 'id' attr and resolve it to the actual hostname and
93126
// add back in as 'node' attr if selected
94127
const nodeId = result.id[1];
@@ -100,6 +133,14 @@ export async function get(
100133
log.trace?.(`get_analytics result:`, JSON.stringify(result));
101134
return result;
102135
});
136+
137+
if (opts?.coalesceTime) {
138+
// coalescing window is the aggregate period plus 10% & converted to milliseconds
139+
const window = envGet(CONFIG_PARAMS.ANALYTICS_AGGREGATEPERIOD) * 1.1 * 1000;
140+
results = await coalesceResults(results, window);
141+
}
142+
143+
return results;
103144
}
104145

105146
type MetricType = 'builtin' | 'custom';
@@ -115,7 +156,10 @@ export function listMetricsOp(req: ListMetricsRequest): Promise<ListMetricsRespo
115156
return listMetrics(req.metric_types, req.custom_metrics_window);
116157
}
117158

118-
export async function listMetrics(metricTypes: MetricType[] = ['builtin'], customWindow: number = defaultCustomMetricWindow): Promise<string[]> {
159+
export async function listMetrics(
160+
metricTypes: MetricType[] = ['builtin'],
161+
customWindow: number = defaultCustomMetricWindow
162+
): Promise<string[]> {
119163
let metrics: string[] = [];
120164

121165
const builtins: BuiltInMetricName[] = Object.values(METRIC);
@@ -126,11 +170,13 @@ export async function listMetrics(metricTypes: MetricType[] = ['builtin'], custo
126170

127171
if (metricTypes.includes('custom')) {
128172
const oldestCustomId = Date.now() - customWindow;
129-
const conditions: Conditions = [{
130-
attribute: 'id',
131-
comparator: 'greater_than',
132-
value: oldestCustomId,
133-
}];
173+
const conditions: Conditions = [
174+
{
175+
attribute: 'id',
176+
comparator: 'greater_than',
177+
value: oldestCustomId,
178+
},
179+
];
134180
const metricConditions = builtins.map((c) => {
135181
return {
136182
attribute: 'metric',

0 commit comments

Comments
 (0)