16
16
17
17
package org .springframework .boot .actuate .metrics ;
18
18
19
- import java .util .Collection ;
19
+ import java .util .ArrayList ;
20
20
import java .util .Collections ;
21
21
import java .util .HashMap ;
22
+ import java .util .LinkedHashMap ;
23
+ import java .util .LinkedHashSet ;
22
24
import java .util .List ;
23
25
import java .util .Map ;
26
+ import java .util .Set ;
27
+ import java .util .function .BiFunction ;
24
28
import java .util .stream .Collectors ;
25
- import java .util .stream .Stream ;
26
29
27
- import io .micrometer .core .instrument .Measurement ;
28
30
import io .micrometer .core .instrument .Meter ;
29
31
import io .micrometer .core .instrument .MeterRegistry ;
30
32
import io .micrometer .core .instrument .Statistic ;
31
33
import io .micrometer .core .instrument .Tag ;
34
+ import io .micrometer .core .instrument .composite .CompositeMeterRegistry ;
32
35
33
36
import org .springframework .boot .actuate .endpoint .annotation .Endpoint ;
34
37
import org .springframework .boot .actuate .endpoint .annotation .ReadOperation ;
40
43
* An {@link Endpoint} for exposing the metrics held by a {@link MeterRegistry}.
41
44
*
42
45
* @author Jon Schneider
46
+ * @author Phillip Webb
43
47
* @since 2.0.0
44
48
*/
45
49
@ Endpoint (id = "metrics" )
@@ -52,54 +56,43 @@ public MetricsEndpoint(MeterRegistry registry) {
52
56
}
53
57
54
58
@ ReadOperation
55
- public Map <String , List <String >> listNames () {
56
- return Collections .singletonMap ("names" , this .registry .getMeters ().stream ()
57
- .map (this ::getMeterIdName ).distinct ().collect (Collectors .toList ()));
59
+ public ListNamesResponse listNames () {
60
+ Set <String > names = new LinkedHashSet <>();
61
+ collectNames (names , this .registry );
62
+ return new ListNamesResponse (names );
58
63
}
59
64
60
- private String getMeterIdName (Meter meter ) {
65
+ private void collectNames (Set <String > names , MeterRegistry registry ) {
66
+ if (registry instanceof CompositeMeterRegistry ) {
67
+ ((CompositeMeterRegistry ) registry ).getRegistries ()
68
+ .forEach ((member ) -> collectNames (names , member ));
69
+ }
70
+ else {
71
+ registry .getMeters ().stream ().map (this ::getName ).forEach (names ::add );
72
+ }
73
+ }
74
+
75
+ private String getName (Meter meter ) {
61
76
return meter .getId ().getName ();
62
77
}
63
78
64
79
@ ReadOperation
65
- public Response metric (@ Selector String requiredMetricName ,
80
+ public MetricResponse metric (@ Selector String requiredMetricName ,
66
81
@ Nullable List <String > tag ) {
67
82
Assert .isTrue (tag == null || tag .stream ().allMatch ((t ) -> t .contains (":" )),
68
83
"Each tag parameter must be in the form key:value" );
69
84
List <Tag > tags = parseTags (tag );
70
- Collection <Meter > meters = this . registry . find ( requiredMetricName ). tags ( tags )
71
- . meters ( );
85
+ List <Meter > meters = new ArrayList <>();
86
+ collectMeters ( meters , this . registry , requiredMetricName , tags );
72
87
if (meters .isEmpty ()) {
73
88
return null ;
74
89
}
75
-
76
- Map <Statistic , Double > samples = new HashMap <>();
77
- Map <String , List <String >> availableTags = new HashMap <>();
78
-
79
- for (Meter meter : meters ) {
80
- for (Measurement ms : meter .measure ()) {
81
- samples .merge (ms .getStatistic (), ms .getValue (), Double ::sum );
82
- }
83
- for (Tag availableTag : meter .getId ().getTags ()) {
84
- availableTags .merge (availableTag .getKey (),
85
- Collections .singletonList (availableTag .getValue ()),
86
- (t1 , t2 ) -> Stream .concat (t1 .stream (), t2 .stream ())
87
- .collect (Collectors .toList ()));
88
- }
89
- }
90
-
90
+ Map <Statistic , Double > samples = getSamples (meters );
91
+ Map <String , List <String >> availableTags = getAvailableTags (meters );
91
92
tags .forEach ((t ) -> availableTags .remove (t .getKey ()));
92
-
93
- return new Response (requiredMetricName ,
94
- samples .entrySet ().stream ()
95
- .map ((sample ) -> new Response .Sample (sample .getKey (),
96
- sample .getValue ()))
97
- .collect (
98
- Collectors .toList ()),
99
- availableTags .entrySet ().stream ()
100
- .map ((tagValues ) -> new Response .AvailableTag (tagValues .getKey (),
101
- tagValues .getValue ()))
102
- .collect (Collectors .toList ()));
93
+ return new MetricResponse (requiredMetricName ,
94
+ asList (samples , MetricResponse .Sample ::new ),
95
+ asList (availableTags , MetricResponse .AvailableTag ::new ));
103
96
}
104
97
105
98
private List <Tag > parseTags (List <String > tags ) {
@@ -109,18 +102,83 @@ private List<Tag> parseTags(List<String> tags) {
109
102
}).collect (Collectors .toList ());
110
103
}
111
104
105
+ private void collectMeters (List <Meter > meters , MeterRegistry registry , String name ,
106
+ Iterable <Tag > tags ) {
107
+ if (registry instanceof CompositeMeterRegistry ) {
108
+ ((CompositeMeterRegistry ) registry ).getRegistries ()
109
+ .forEach ((member ) -> collectMeters (meters , member , name , tags ));
110
+ }
111
+ else {
112
+ meters .addAll (registry .find (name ).tags (tags ).meters ());
113
+ }
114
+ }
115
+
116
+ private Map <Statistic , Double > getSamples (List <Meter > meters ) {
117
+ Map <Statistic , Double > samples = new LinkedHashMap <>();
118
+ meters .forEach ((meter ) -> mergeMeasurements (samples , meter ));
119
+ return samples ;
120
+ }
121
+
122
+ private void mergeMeasurements (Map <Statistic , Double > samples , Meter meter ) {
123
+ meter .measure ().forEach ((measurement ) -> samples .merge (measurement .getStatistic (),
124
+ measurement .getValue (), Double ::sum ));
125
+ }
126
+
127
+ private Map <String , List <String >> getAvailableTags (List <Meter > meters ) {
128
+ Map <String , List <String >> availableTags = new HashMap <>();
129
+ meters .forEach ((meter ) -> mergeAvailableTags (availableTags , meter ));
130
+ return availableTags ;
131
+ }
132
+
133
+ private void mergeAvailableTags (Map <String , List <String >> availableTags ,
134
+ Meter meter ) {
135
+ meter .getId ().getTags ().forEach ((tag ) -> {
136
+ List <String > value = Collections .singletonList (tag .getValue ());
137
+ availableTags .merge (tag .getKey (), value , this ::merge );
138
+ });
139
+ }
140
+
141
+ private <T > List <T > merge (List <T > list1 , List <T > list2 ) {
142
+ List <T > result = new ArrayList <>(list1 .size () + list2 .size ());
143
+ result .addAll (list1 );
144
+ result .addAll (list2 );
145
+ return result ;
146
+ }
147
+
148
+ private <K , V , T > List <T > asList (Map <K , V > map , BiFunction <K , V , T > mapper ) {
149
+ return map .entrySet ().stream ()
150
+ .map ((entry ) -> mapper .apply (entry .getKey (), entry .getValue ()))
151
+ .collect (Collectors .toCollection (ArrayList ::new ));
152
+ }
153
+
154
+ /**
155
+ * Response payload for a metric name listing.
156
+ */
157
+ static class ListNamesResponse {
158
+
159
+ private final Set <String > names ;
160
+
161
+ ListNamesResponse (Set <String > names ) {
162
+ this .names = names ;
163
+ }
164
+
165
+ public Set <String > getNames () {
166
+ return this .names ;
167
+ }
168
+ }
169
+
112
170
/**
113
- * Response payload.
171
+ * Response payload for a metric name selector .
114
172
*/
115
- static class Response {
173
+ static class MetricResponse {
116
174
117
175
private final String name ;
118
176
119
177
private final List <Sample > measurements ;
120
178
121
179
private final List <AvailableTag > availableTags ;
122
180
123
- Response (String name , List <Sample > measurements ,
181
+ MetricResponse (String name , List <Sample > measurements ,
124
182
List <AvailableTag > availableTags ) {
125
183
this .name = name ;
126
184
this .measurements = measurements ;
@@ -189,6 +247,8 @@ public String toString() {
189
247
return "MeasurementSample{" + "statistic=" + this .statistic + ", value="
190
248
+ this .value + '}' ;
191
249
}
250
+
192
251
}
252
+
193
253
}
194
254
}
0 commit comments