Skip to content

Commit d5799bc

Browse files
committed
mk-oracle: add handling for per-instance custom_metrics
Allow the `custom_metrics` key at the per-instance level. Merge per-instance custom metrics with global custom metrics, if the same `item_name` is found at both global and per-instance levels then the `item_name` at the per-instance level wins. CMK-34048 Change-Id: Id20d9492a0aea4d3f1dccac811e6d7cecd670c97
1 parent 7f0f71f commit d5799bc

4 files changed

Lines changed: 160 additions & 16 deletions

File tree

packages/mk-oracle/src/config/ora_sql.rs

Lines changed: 98 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515
// SPDX-License-Identifier: Apache-2.0
1616

1717
use super::defines::{defaults, keys, values};
18-
use super::section::{Section, SectionKind, Sections};
18+
use super::section::{names, Section, SectionKind, Sections};
1919
use super::yaml::{Get, Yaml};
2020
use crate::config::authentication::Authentication;
2121
use crate::config::connection::Connection;
2222
use crate::config::options::Options;
2323
use crate::config::target::TargetId;
2424
use crate::ora_sql::detect::get_local_sid_names;
2525
use crate::types::{
26-
DescriptorSid, HostName, InstanceAlias, InstanceName, ServiceName, ServiceType, Sid,
27-
SqlBindParam,
26+
DescriptorSid, HostName, InstanceAlias, InstanceName, SectionName, ServiceName, ServiceType,
27+
Sid, SqlBindParam,
2828
};
2929
use anyhow::{anyhow, bail, Context, Result};
3030
use std::collections::hash_map::DefaultHasher;
@@ -388,6 +388,7 @@ pub struct CustomInstance {
388388
target_id: Option<TargetId>,
389389
alias: Option<InstanceAlias>,
390390
piggyback: Option<Piggyback>,
391+
custom_metrics: Vec<Section>,
391392
}
392393

393394
impl CustomInstance {
@@ -404,6 +405,7 @@ impl CustomInstance {
404405
target_id,
405406
alias,
406407
piggyback,
408+
custom_metrics: vec![],
407409
}
408410
}
409411

@@ -413,13 +415,23 @@ impl CustomInstance {
413415
main_conn: &Connection,
414416
sections: &Sections,
415417
) -> Result<Self> {
416-
Ok(Self::new(
417-
ensure_auth(yaml, main_auth)?,
418-
ensure_conn(yaml, main_conn)?,
419-
TargetId::from_yaml(yaml)?,
420-
yaml.get_string(keys::ALIAS).map(InstanceAlias::from),
421-
Piggyback::from_yaml(yaml, sections)?,
422-
))
418+
Ok(Self {
419+
auth: ensure_auth(yaml, main_auth)?,
420+
conn: ensure_conn(yaml, main_conn)?,
421+
target_id: TargetId::from_yaml(yaml)?,
422+
alias: yaml.get_string(keys::ALIAS).map(InstanceAlias::from),
423+
piggyback: Piggyback::from_yaml(yaml, sections)?,
424+
custom_metrics: Sections::get_sections(
425+
yaml.get(keys::CUSTOM_METRICS),
426+
Some(defaults::CUSTOM_METRIC_SEPARATOR),
427+
Some(&SectionName::from(names::CUSTOM_METRIC.to_string())),
428+
)
429+
.unwrap_or_default(),
430+
})
431+
}
432+
433+
pub fn custom_metrics(&self) -> &[Section] {
434+
&self.custom_metrics
423435
}
424436

425437
/// may be overridden with a connection value
@@ -547,10 +559,9 @@ oracle:
547559
timeout: 11 # optional, default 5
548560
tns_admin: "/path/to/oracle/config/files/" # optional, default: agent plugin config folder. Points to the location of sqlnet.ora and tnsnames.ora
549561
oracle_local_registry: "/etc/oracle/olr.loc" # optional, default: folder of oracle configuration files like oratab
550-
custom_metrics: # additional queries which generates <<<oracle_sql>>>> + item [SID|name] for each instance
551-
- my_custom_metric: # for item generation, mandatory
552-
sql: "select * from dual" # optional
553-
is_async: no # optional, default: no
562+
custom_metrics: # additional queries that produce <<<oracle_sql:sep(58)>>> + [[[SID|item_name]]] for each instance
563+
- my_custom_metric: # item name (mandatory); becomes the subsection item
564+
sql: "select 'details:hello' from dual" # inline SQL executed against the instance
554565
sections: # optional
555566
- instance: # special section
556567
affinity: "all" # optional, default: "db", values: "all", "db", "asm"
@@ -758,6 +769,8 @@ piggyback:
758769
custom.item_value().map(|v| v.as_str()),
759770
Some("my_custom_metric")
760771
);
772+
assert_eq!(custom.sql(), Some("select 'details:hello' from dual"));
773+
assert_eq!(custom.sep(), defaults::CUSTOM_METRIC_SEPARATOR);
761774

762775
product.sections().iter().for_each(|s| {
763776
if s.name().as_str() == names::CUSTOM_METRIC {
@@ -773,6 +786,77 @@ piggyback:
773786
assert_eq!(c.mode, Mode::Special);
774787
}
775788

789+
#[test]
790+
fn test_per_instance_custom_metrics_parsed_and_override_globals() {
791+
const YAML: &str = r#"
792+
oracle:
793+
main:
794+
authentication:
795+
username: u
796+
password: p
797+
type: standard
798+
connection:
799+
hostname: localhost
800+
custom_metrics:
801+
- shared_metric:
802+
sql: "select 'details:GLOBAL' from dual"
803+
- global_only:
804+
sql: "select 'details:global_only' from dual"
805+
instances:
806+
- service_name: INST_A
807+
custom_metrics:
808+
- shared_metric:
809+
sql: "select 'details:PER_INSTANCE' from dual"
810+
- instance_only:
811+
sql: "select 'details:instance_only' from dual"
812+
- service_name: INST_B
813+
"#;
814+
let c = Config::from_string(YAML).unwrap().unwrap();
815+
// Global custom_metrics live on Sections — both global entries present.
816+
let global_custom: Vec<_> = c
817+
.product()
818+
.sections()
819+
.iter()
820+
.filter_map(|s| s.item_value().map(|v| (v.as_str(), s.sql())))
821+
.collect();
822+
assert!(
823+
global_custom.contains(&("shared_metric", Some("select 'details:GLOBAL' from dual"))),
824+
"global custom_metrics: {global_custom:?}"
825+
);
826+
assert!(
827+
global_custom.contains(&(
828+
"global_only",
829+
Some("select 'details:global_only' from dual")
830+
)),
831+
"global custom_metrics: {global_custom:?}"
832+
);
833+
834+
let instances = c.instances();
835+
let inst_a = &instances[0];
836+
let inst_a_metrics: Vec<_> = inst_a
837+
.custom_metrics()
838+
.iter()
839+
.map(|s| (s.item_value().unwrap().as_str(), s.sql()))
840+
.collect();
841+
// Per-instance: same shared_metric name, but per-instance SQL.
842+
assert!(
843+
inst_a_metrics.contains(&(
844+
"shared_metric",
845+
Some("select 'details:PER_INSTANCE' from dual")
846+
)),
847+
"per-instance metrics for INST_A: {inst_a_metrics:?}"
848+
);
849+
assert!(
850+
inst_a_metrics.contains(&(
851+
"instance_only",
852+
Some("select 'details:instance_only' from dual")
853+
)),
854+
"per-instance metrics for INST_A: {inst_a_metrics:?}"
855+
);
856+
// INST_B has no per-instance custom_metrics.
857+
assert!(instances[1].custom_metrics().is_empty());
858+
}
859+
776860
#[test]
777861
fn test_config_connection() {
778862
let c = Config::from_string(data::TEST_CONFIG).unwrap().unwrap();

packages/mk-oracle/src/ora_sql/instance.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,13 @@ pub async fn generate_data(
130130
.collect();
131131

132132
let (root_spots, root_errors) = connect_spots(all, None);
133-
let (work_spots, work_errors) = make_spot_work_results(root_spots, sections, ora_sql.params());
133+
let (work_spots, work_errors) = make_spot_work_results(
134+
root_spots,
135+
sections,
136+
ora_sql.instances(),
137+
ora_sql.product().cache_age(),
138+
ora_sql.params(),
139+
);
134140
let results = if ora_sql.options().threads() > 1 {
135141
process_spot_works_para(work_spots, ora_sql.options().threads())
136142
} else {

packages/mk-oracle/src/ora_sql/spots.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
//
1515
// SPDX-License-Identifier: Apache-2.0
1616

17+
use crate::config::ora_sql::CustomInstance;
1718
use crate::ora_sql::backend::{ClosedSpot, OpenedSpot};
1819
use crate::ora_sql::custom::get_sql_dir;
1920
use crate::ora_sql::section::Section;
@@ -49,16 +50,20 @@ type SpotErrors = (ClosedSpot, anyhow::Error);
4950
pub fn make_spot_work_results(
5051
spots: Vec<OpenedSpot>,
5152
sections: Vec<Section>,
53+
custom_instances: &[CustomInstance],
54+
global_cache_age: u32,
5255
params: &[SqlBindParam],
5356
) -> (Vec<SpotWorks>, Vec<SpotErrors>) {
5457
let work_results = spots
5558
.into_iter()
5659
.map(|spot| {
5760
let instance_candidates = WorkInstances::new(&spot, None);
5861
let closed = spot.close();
62+
let merged_sections =
63+
merge_per_instance_sections(&sections, &closed, custom_instances, global_cache_age);
5964
match instance_candidates {
6065
Err(ref e) => _make_closed_error(closed, e),
61-
Ok(instances) => _make_closed_ok(closed, instances, &sections, params),
66+
Ok(instances) => _make_closed_ok(closed, instances, &merged_sections, params),
6267
}
6368
})
6469
.collect::<Vec<SpotWorkResults>>();
@@ -75,6 +80,51 @@ pub fn make_spot_work_results(
7580
)
7681
}
7782

83+
/// Merge the global section list with per-instance `custom_metrics` of the
84+
/// `CustomInstance` whose target matches this spot. Per-instance entries
85+
/// override global entries that share the same `item_value` (per tech design:
86+
/// "If a global and a per-instance query share the same item_name, the
87+
/// per-instance one wins.").
88+
fn merge_per_instance_sections(
89+
global: &[Section],
90+
spot: &ClosedSpot,
91+
custom_instances: &[CustomInstance],
92+
global_cache_age: u32,
93+
) -> Vec<Section> {
94+
let spot_target_id = spot.target().target_id();
95+
let per_instance_runtime: Vec<Section> = custom_instances
96+
.iter()
97+
.find(|custom_instance| custom_instance.target_id() == spot_target_id)
98+
.map(|custom_instance| {
99+
custom_instance
100+
.custom_metrics()
101+
.iter()
102+
.map(|cs| Section::new(cs, global_cache_age))
103+
.collect()
104+
})
105+
.unwrap_or_default();
106+
107+
if per_instance_runtime.is_empty() {
108+
return global.to_vec();
109+
}
110+
let overridden_items: std::collections::HashSet<&str> = per_instance_runtime
111+
.iter()
112+
.filter_map(|s| s.item_value().map(|v| v.as_str()))
113+
.collect();
114+
115+
let mut merged: Vec<Section> = global
116+
.iter()
117+
.filter(|s| {
118+
s.item_value()
119+
.map(|v| !overridden_items.contains(v.as_str()))
120+
.unwrap_or(true)
121+
})
122+
.cloned()
123+
.collect();
124+
merged.extend(per_instance_runtime);
125+
merged
126+
}
127+
78128
fn _make_closed_error(closed: ClosedSpot, e: &anyhow::Error) -> SpotWorkResults {
79129
let target = closed.target().clone();
80130
log::error!("Failed to get instances for spot {:?}: {}", target, e);

packages/mk-oracle/src/ora_sql/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ impl Target {
6868
pub fn service_type(&self) -> Option<&ServiceType> {
6969
self.target_id.as_ref().and_then(|t| t.service_type())
7070
}
71+
72+
pub fn target_id(&self) -> Option<&TargetId> {
73+
self.target_id.as_ref()
74+
}
7175
}
7276

7377
impl Target {

0 commit comments

Comments
 (0)