-
Notifications
You must be signed in to change notification settings - Fork 172
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add spectator-web-spring subproject (#318)
This module adds a `/spectator/metrics` HTTP endpoint that returns a different JSON encoding than spring boot's `/metrics` endpoint. The JSON encoding contains a richer representation of the registry to enlighten external observers. The endpoint supports some filtering mechanisms to restrict the domain of metrics exposed if the caller is only interested in particular metrics.
- Loading branch information
1 parent
1906c5e
commit 53201cf
Showing
16 changed files
with
2,108 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright 2016 Google, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
dependencies { | ||
compile project(':spectator-api') | ||
|
||
compile 'org.springframework.boot:spring-boot-starter-actuator:1.2.8.RELEASE' | ||
compile 'org.springframework.boot:spring-boot-starter-data-rest:1.2.8.RELEASE' | ||
compile 'com.fasterxml.jackson.core:jackson-databind:2.5.3' | ||
} |
202 changes: 202 additions & 0 deletions
202
spectator-web-spring/src/main/java/com/netflix/spectator/controllers/MetricsController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/* | ||
* Copyright 2016 Google, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License") | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.netflix.spectator.controllers; | ||
|
||
import com.netflix.spectator.api.Gauge; | ||
import com.netflix.spectator.api.Id; | ||
import com.netflix.spectator.api.Measurement; | ||
import com.netflix.spectator.api.Meter; | ||
import com.netflix.spectator.api.Registry; | ||
|
||
import com.netflix.spectator.controllers.filter.PrototypeMeasurementFilter; | ||
import com.netflix.spectator.controllers.filter.TagMeasurementFilter; | ||
import com.netflix.spectator.controllers.model.ApplicationRegistry; | ||
import com.netflix.spectator.controllers.model.MetricValues; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
|
||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestMethod; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import java.io.IOException; | ||
import java.util.function.Predicate; | ||
import java.util.Map; | ||
import java.util.HashMap; | ||
|
||
|
||
/** | ||
* Provides an HTTP endpoint for polling spectator metrics. | ||
*/ | ||
@RequestMapping("/spectator/metrics") | ||
@RestController | ||
@ConditionalOnExpression("${spectator.webEndpoint.enabled:false}") | ||
public class MetricsController { | ||
@Autowired | ||
private Registry registry; | ||
|
||
/** | ||
* A measurement filter that accepts all measurements. | ||
*/ | ||
public static final Predicate<Measurement> ALL_MEASUREMENTS_FILTER = m -> true; | ||
|
||
@Value("${spectator.applicationName:}") | ||
private String applicationName; | ||
|
||
@Value("${spectator.applicationVersion:}") | ||
private String applicationVersion; | ||
|
||
@Value("${spectator.webEndpoint.prototypeFilter.path:}") | ||
private String prototypeFilterPath; | ||
|
||
private Map<Id, String> knownMeterKinds = new HashMap<Id, String>(); | ||
private Predicate<Measurement> defaultMeasurementFilter = null; | ||
|
||
/** | ||
* The default measurement filter is configured through properties. | ||
*/ | ||
public Predicate<Measurement> getDefaultMeasurementFilter() throws IOException { | ||
if (defaultMeasurementFilter != null) { | ||
return defaultMeasurementFilter; | ||
} | ||
|
||
if (!prototypeFilterPath.isEmpty()) { | ||
defaultMeasurementFilter = PrototypeMeasurementFilter.loadFromPath( | ||
prototypeFilterPath); | ||
} else { | ||
defaultMeasurementFilter = ALL_MEASUREMENTS_FILTER; | ||
} | ||
|
||
return defaultMeasurementFilter; | ||
} | ||
|
||
|
||
/** | ||
* Endpoint for querying current metric values. | ||
* | ||
* The result is a JSON document describing the metrics and their values. | ||
* The result can be filtered using query parameters or configuring the | ||
* controller instance itself. | ||
*/ | ||
@RequestMapping(method = RequestMethod.GET) | ||
public ApplicationRegistry getMetrics(@RequestParam Map<String, String> filters) | ||
throws IOException { | ||
boolean all = filters.get("all") != null; | ||
String filterMeterNameRegex = filters.getOrDefault("meterNameRegex", ""); | ||
String filterTagNameRegex = filters.getOrDefault("tagNameRegex", ""); | ||
String filterTagValueRegex = filters.getOrDefault("tagValueRegex", ""); | ||
TagMeasurementFilter queryFilter = new TagMeasurementFilter( | ||
filterMeterNameRegex, filterTagNameRegex, filterTagValueRegex); | ||
Predicate<Measurement> filter; | ||
if (all) { | ||
filter = queryFilter; | ||
} else { | ||
filter = queryFilter.and(getDefaultMeasurementFilter()); | ||
} | ||
|
||
ApplicationRegistry response = new ApplicationRegistry(); | ||
response.setApplicationName(applicationName); | ||
response.setApplicationVersion(applicationVersion); | ||
response.setMetrics(encodeRegistry(registry, filter)); | ||
return response; | ||
} | ||
|
||
/** | ||
* Internal API for encoding a registry that can be encoded as JSON. | ||
* This is a helper function for the REST endpoint and to test against. | ||
*/ | ||
Map<String, MetricValues> encodeRegistry( | ||
Registry sourceRegistry, Predicate<Measurement> filter) { | ||
Map<String, MetricValues> metricMap = new HashMap<String, MetricValues>(); | ||
|
||
/** | ||
* Flatten the meter measurements into a map of measurements keyed by | ||
* the name and mapped to the different tag variants. | ||
*/ | ||
for (Meter meter : sourceRegistry) { | ||
String kind = knownMeterKinds.computeIfAbsent( | ||
meter.id(), k -> meterToKind(sourceRegistry, meter)); | ||
|
||
for (Measurement measurement : meter.measure()) { | ||
if (!filter.test(measurement)) { | ||
continue; | ||
} | ||
|
||
String measurementName = measurement.id().name(); | ||
MetricValues have = metricMap.get(measurementName); | ||
if (have == null) { | ||
metricMap.put(measurementName, new MetricValues(kind, measurement)); | ||
} else { | ||
have.addMeasurement(measurement); | ||
} | ||
} | ||
} | ||
return metricMap; | ||
} | ||
|
||
/** | ||
* Determine the type of a meter for reporting purposes. | ||
* | ||
* The accuracy of this method is sensitive to the registry value. | ||
* In practice meters are often AggrMeter or CompositeMeter so it | ||
* is not worth testing the class against the interface because it | ||
* is rarely an actual kind. For lack of a direct way to query | ||
* we will iterate over all the instances in the registry of a given | ||
* type using the registry stream getters. | ||
* | ||
* 20160918(ewiseblatt): | ||
* However, this doesnt work either unless the registry instance | ||
* we have is exactly right and spring seems to get in the way. | ||
* If the registry was a bean, you might need to autowire it. | ||
* But it isnt clear if all the metrics are created through that | ||
* registry. The Spectator.globalRegistry, for example, iterates over | ||
* all the meters using the generic iterator, but the kind-specific | ||
* stream functions are empty if the registry was created as a spring | ||
* bean so you need to passing in an @autowired registry. | ||
* | ||
* @param registry | ||
* Used to provide supplemental information (e.g. to search for the meter). | ||
* | ||
* @param meter | ||
* The meters whose kind we want to know. | ||
* | ||
* @return | ||
* A string such as "Counter". If the type cannot be identified as one of | ||
* the standard Spectator api interface variants, then the simple class name | ||
* is returned. | ||
*/ | ||
public static String meterToKind(Registry registry, Meter meter) { | ||
String kind; | ||
if (registry.counters() | ||
.anyMatch(m -> m.id().equals(meter.id()))) { | ||
kind = "Counter"; | ||
} else if (registry.timers() | ||
.anyMatch(m -> m.id().equals(meter.id()))) { | ||
kind = "Timer"; | ||
} else if (registry.distributionSummaries() | ||
.anyMatch(m -> m.id().equals(meter.id()))) { | ||
kind = "Distribution"; | ||
} else if (meter instanceof Gauge) { | ||
kind = "Gauge"; | ||
} else { | ||
kind = meter.getClass().getSimpleName(); | ||
} | ||
return kind; | ||
} | ||
} |
Oops, something went wrong.