Skip to content

Commit c879aea

Browse files
atheurerclaude
andcommitted
Add support for multiple values in --breakout option
Enhanced the --breakout option in get-metric-data.js to support comma-separated values (e.g., --breakout hostname=a,b,c) which returns separate metrics for each specified value. This addresses issue #110. Changes: - Modified list() parser to distinguish between field separators and value lists - Updated OpenSearch query builder to use "terms" query for multiple values - Added documentation with examples and usage guidelines The implementation maintains backward compatibility and is designed to support future aggregation syntax (e.g., hostname=a+b). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 48dfa8b commit c879aea

3 files changed

Lines changed: 82 additions & 2 deletions

File tree

queries/cdmq/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,29 @@ When evaluating these breakouts, we can see that `<1>-<worker>-<physical>-<tx>`
383383
},
384384
"breakouts": []
385385
}
386+
387+
#### Specifying Multiple Values for a Breakout
388+
389+
In addition to filtering a breakout to a single value (e.g., `csid=1`), you can now specify multiple values for a breakout field using comma-separated values. This will return separate metrics for each specified value.
390+
391+
For example, to get metrics for both worker nodes 1 and 2:
392+
393+
# node ./get-metric-data.js --period 4F1014D6-AD33-11EC-94E3-ADE96E3275F7 --source sar-net --type L2-Gbps --breakout csid=1,2,cstype=worker,type=physical
394+
395+
This will return two separate metrics: one for `csid=1` and one for `csid=2`, without including metrics for any other csid values that might exist in the data.
396+
397+
**Important**: The comma separator has different meanings depending on context:
398+
- Between different breakout fields: `csid,cstype` means break out by both csid AND cstype
399+
- Within a value list: `csid=1,2` means break out by csid, but only include values 1 and 2
400+
- Mixed usage: `csid=1,2,cstype=worker` means break out by csid (only values 1,2) and cstype (only value worker)
401+
402+
This feature is particularly useful when:
403+
- You want to compare specific hosts or components without seeing all possible values
404+
- You need to reduce output by focusing on a subset of values
405+
- You want to query multiple specific values in a single command instead of running separate queries
406+
407+
**Note**: Each comma-separated value in a breakout filter (e.g., `csid=1,2`) will produce separate metrics in the output, not an aggregated metric. Future enhancements may support aggregation using a different syntax (e.g., `csid=1+2`).
408+
386409
So far all of the metrics have been represented as a single value for a specific time period. When `--period` is used, the script finds the begin and end times for this period, which in most cases, has a duration equal to the measurement time in the benchmark itself (around 90 seconds in these examples). One can also specify `--run`, `--begin`, and `--end` instead of `--period`, should they need to focus on a different period of time. However, for benchmark metrics (such as uperf), it is important to limit the begin and end to within the actual measurement period for that sample. Conversely, tool metrics can use a begin and end spanning any time period within the run, as the tool collection tends to run continuously for any particular run. Whatever time period is used, one can also use `--resolution` to divide this time period into multiple data-samples, in order to generate things like line graphs:
387410

388411
# node ./get-metric-data.js --period 4F1014D6-AD33-11EC-94E3-ADE96E3275F7 --source sar-net --type L2-Gbps --breakout csid=1,cstype=worker,type=physical,direction=tx,dev --filter gt:0.01 --resolution 10

queries/cdmq/cdm.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2609,14 +2609,25 @@ getMetricGroupsFromBreakouts = async function (instance, sets, yearDotMonth) {
26092609
q.query.bool.filter.push(JSON.parse('{"term": {"run.run-uuid": "' + set.run + '"}}'));
26102610
}
26112611
// If the breakout contains a match requirement (something like "host=myhost"), then we must add a term filter for it.
2612+
// Multiple values can be specified with commas: "host=a,b,c" which will match any of those values.
26122613
// Eventually it would be nice to have something other than a match, like a regex: host=/^client/.
26132614
var regExp = /([^\=]+)\=([^\=]+)/;
26142615
set.breakout.forEach((field) => {
26152616
var matches = regExp.exec(field);
26162617
if (matches) {
26172618
field = matches[1];
26182619
value = matches[2];
2619-
q.query.bool.filter.push(JSON.parse('{"term": {"metric_desc.names.' + field + '": "' + value + '"}}'));
2620+
// Check if the value contains multiple comma-separated values
2621+
var values = value.split(',');
2622+
if (values.length > 1) {
2623+
// Multiple values: use "terms" query (note the plural)
2624+
q.query.bool.filter.push(
2625+
JSON.parse('{"terms": {"metric_desc.names.' + field + '": ' + JSON.stringify(values) + '}}')
2626+
);
2627+
} else {
2628+
// Single value: use "term" query (singular)
2629+
q.query.bool.filter.push(JSON.parse('{"term": {"metric_desc.names.' + field + '": "' + value + '"}}'));
2630+
}
26202631
}
26212632
});
26222633
q.aggs = aggs;

queries/cdmq/get-metric-data.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,53 @@ var sprintf = require('sprintf-js').sprintf;
1515
var instances = []; // opensearch instances
1616

1717
function list(val) {
18-
return val.split(',');
18+
// Parse breakout string to handle both:
19+
// - Simple breakouts: "hostname,cpu" -> ["hostname", "cpu"]
20+
// - Breakouts with values: "hostname=a,cpu=x" -> ["hostname=a", "cpu=x"]
21+
// - Breakouts with multiple values: "hostname=a,b,cpu=x,y" -> ["hostname=a,b", "cpu=x,y"]
22+
//
23+
// The key insight: a comma separates breakout fields UNLESS we're currently
24+
// parsing a value list (after '=' and before the next field with '=')
25+
26+
var result = [];
27+
var current = '';
28+
var inValueList = false;
29+
var parts = val.split(',');
30+
31+
for (var i = 0; i < parts.length; i++) {
32+
var part = parts[i];
33+
var hasEqual = part.includes('=');
34+
35+
if (inValueList && !hasEqual) {
36+
// We're in a value list and this part doesn't have '=', so it's another value
37+
current += ',' + part;
38+
} else if (inValueList && hasEqual) {
39+
// We were in a value list, but now we hit a new key=value pair
40+
result.push(current);
41+
current = part;
42+
inValueList = true;
43+
} else if (!inValueList && hasEqual) {
44+
// Starting a new key=value pair
45+
if (current !== '') {
46+
result.push(current);
47+
}
48+
current = part;
49+
inValueList = true;
50+
} else {
51+
// !inValueList && !hasEqual - simple breakout field without value filter
52+
if (current !== '') {
53+
result.push(current);
54+
}
55+
current = part;
56+
inValueList = false;
57+
}
58+
}
59+
60+
if (current !== '') {
61+
result.push(current);
62+
}
63+
64+
return result;
1965
}
2066

2167
function save_host(host) {

0 commit comments

Comments
 (0)