22
33import java .util .ArrayList ;
44import java .util .Arrays ;
5+ import java .util .BitSet ;
56import java .util .Collections ;
67import java .util .Comparator ;
8+ import java .util .HashMap ;
79import java .util .List ;
810import java .util .Map ;
911import java .util .Objects ;
@@ -30,37 +32,68 @@ public class SensorMetadata {
3032 *
3133 * @param components a map describing the metadata for each component
3234 * @param documentation a text providing any relevant information associated with the described sensor
33- * @param totalComponents an array of indices indicating which components can be used to compute a total power consumption
34- * metric for that sensor. Must use a unit commensurable with {@link SensorUnit#W}
3535 * @throws IllegalArgumentException if indices specified in {@code totalComponents} do not represent power measures
3636 * expressible in Watts or are not a valid index
3737 */
38- @ JsonCreator
39- public SensorMetadata (@ JsonProperty ("metadata" ) Map <String , ComponentMetadata > components ,
40- @ JsonProperty ("documentation" ) String documentation ,
41- @ JsonProperty ("totalComponents" ) int [] totalComponents ) {
42- this .components = Objects .requireNonNull (components , "Must provide components" );
38+ public SensorMetadata (List <ComponentMetadata > components , String documentation ) {
39+ Objects .requireNonNull (components , "Must provide components" );
40+ final var cardinality = components .size ();
41+ this .components = new HashMap <>(cardinality );
4342 this .documentation = documentation ;
44- this .totalComponents = Objects .requireNonNull (totalComponents , "Must provide total components" );
4543 final var errors = new Errors ();
46- for (int index : totalComponents ) {
47- if (index < 0 ) {
48- errors .addError (index + " is not a valid index" );
49- continue ;
44+ final var indices = new BitSet (cardinality );
45+ components .forEach (component -> {
46+ // check that index is valid
47+ final var index = component .index ;
48+ if (index < 0 || index >= cardinality ) {
49+ errors .addError (index + " is not a valid index: must be between 0 and " + (cardinality - 1 ));
50+ } else if (indices .get (index )) {
51+ errors .addError ("Multiple components are using index " + index + ": "
52+ + components .stream ().filter (cm -> index == cm .index ).toList ());
53+ } else {
54+ // record index as known
55+ indices .set (index );
56+ }
57+
58+ // check that component's unit is commensurable to Watts if included in total
59+ if (component .isIncludedInTotal && !component .isWattCommensurable ()) {
60+ errors .addError ("Component " + component .name
61+ + " is not commensurate with a power measure. It needs to be expressible in Watts." );
5062 }
51- components .values ().stream ()
52- .filter (cm -> index == cm .index )
53- .findFirst ()
54- .ifPresentOrElse (component -> {
55- if (!component .isWattCommensurable ()) {
56- errors .addError ("Component " + component .name
57- + " is not commensurate with a power measure. It needs to be expressible in Watts." );
58- }
59- }, () -> errors .addError (index + " is not a valid index" ));
63+
64+ if (this .components .containsKey (component .name )) {
65+ errors .addError ("Multiple components are named '" + component .name + "': "
66+ + components .stream ().filter (cm -> cm .name .equals (component .name )).toList ());
67+ } else {
68+ this .components .put (component .name , component );
69+ }
70+ });
71+
72+ // verify that all indices are covered
73+ if (indices .cardinality () != cardinality ) {
74+ indices .flip (0 , cardinality );
75+ errors .addError (
76+ "Components' indices should cover the full range of 0 to " + (cardinality - 1 ) + ". Missing indices: "
77+ + indices );
6078 }
79+
6180 if (errors .hasErrors ()) {
6281 throw new IllegalArgumentException (errors .formatErrors ());
6382 }
83+
84+ this .totalComponents = indices .stream ().toArray ();
85+ }
86+
87+ @ JsonCreator
88+ SensorMetadata (Map <String , ComponentMetadata > components , String documentation , int [] totalComponents ) {
89+ this .components = components ;
90+ this .documentation = documentation ;
91+ this .totalComponents = totalComponents ;
92+ }
93+
94+ public static SensorMetadata .Builder withNewComponent (String name , String description , boolean isAttributed , String unit ,
95+ boolean participatesInTotal ) {
96+ return new SensorMetadata .Builder ().withNewComponent (name , description , isAttributed , unit , participatesInTotal );
6497 }
6598
6699 @ Override
@@ -136,6 +169,27 @@ public int[] totalComponents() {
136169 return totalComponents ;
137170 }
138171
172+ public static class Builder {
173+ private final List <ComponentMetadata > components = new ArrayList <>();
174+ private int currentIndex = 0 ;
175+ private String documentation ;
176+
177+ public Builder withNewComponent (String name , String description , boolean isAttributed , String unit ,
178+ boolean isIncludedInTotal ) {
179+ components .add (new ComponentMetadata (name , currentIndex ++, description , isAttributed , unit , isIncludedInTotal ));
180+ return this ;
181+ }
182+
183+ public Builder withDocumentation (String documentation ) {
184+ this .documentation = documentation ;
185+ return this ;
186+ }
187+
188+ public SensorMetadata build () {
189+ return new SensorMetadata (components , documentation );
190+ }
191+ }
192+
139193 private static class Errors {
140194 private List <String > errors ;
141195
@@ -171,16 +225,27 @@ String formatErrors() {
171225 * attributed share for each process needs to be performed. This is needed because some sensors only provide
172226 * system-wide measures instead of on a per-process basis.
173227 * @param unit a textual representation of the unit used for measures associated with this component (e.g. mW)
228+ * @param isIncludedInTotal whether or not this component takes part in the computation to get a total power consumption
229+ * metric for that sensor. Components that take part of the total computation must use a unit commensurable with
230+ * {@link SensorUnit#W}
174231 */
175- public record ComponentMetadata (String name , int index , String description , boolean isAttributed , String unit ) {
232+ public record ComponentMetadata (String name , int index , String description , boolean isAttributed , String unit ,
233+ boolean isIncludedInTotal ) {
234+
235+ public ComponentMetadata {
236+ if (name == null ) {
237+ throw new IllegalArgumentException ("Component name cannot be null" );
238+ }
239+ }
240+
176241 /**
177242 * Determines whether or not this component is measuring power (i.e. its value can be converted to Watts)
178243 *
179244 * @return {@code true} if this component's unit is commensurable to Watts, {@code false} otherwise
180245 */
181246 @ JsonIgnore
182247 public boolean isWattCommensurable () {
183- return SensorUnit .of (unit ).isWattCommensurable ();
248+ return unit != null && SensorUnit .of (unit ).isWattCommensurable ();
184249 }
185250 }
186251}
0 commit comments