in agent/stats/envoy_prometheus_stats.go [66:162]
func (envoyPrometheusStatsHandler *EnvoyPrometheusStatsHandler) HandleStats(resWriter http.ResponseWriter, request *http.Request) {
if !envoyPrometheusStatsHandler.Limiter.Allow() {
http.Error(resWriter, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
return
}
// Validate the query parameters in the request before passing it to call Envoy stats endpoint.
// Extract queryStringToEnvoy from request and generate the stats url sent to Envoy stats endpoint.
//
// Note that we always prepend the ENVOY_PROMETHEUS_QUERY_STRING to enforce the prometheus format.
// See https://www.envoyproxy.io/docs/envoy/latest/operations/admin#get--stats?format=prometheus
if queryParams, err := validateGetStatsRequest(request); err != nil {
http.Error(resWriter, err.Error(), http.StatusBadRequest)
return
} else {
envoyPrometheusStatsHandler.QueryParameters = *queryParams
}
// If the Delta exists, we would just return Delta directly with no further operation needed.
if envoyPrometheusStatsHandler.QueryParameters.Has(deltaQueryKey) && envoyPrometheusStatsHandler.Snapshotter.Delta != nil {
resWriter.WriteHeader(http.StatusOK)
err := writeMetricsToResponse(resWriter, envoyPrometheusStatsHandler.Snapshotter.Delta)
if err != nil {
log.Errorf("error while writing response: %s", err)
}
return
}
// If Delta is not yet computed, most likely it is too early and we don't yet have two snapshots to compute the
// delta. In this case we will just return the stats as it is because that is essentially the delta
// (current stats - 0).
//
// Start building the request to Envoy Admin Interface for the stats.
queryStringToEnvoy := constructQueryString(envoyPrometheusStatsHandler.QueryParameters)
envoyStatsUrl := getEnvoyStatsUrl(&envoyPrometheusStatsHandler.AgentConfig, queryStringToEnvoy)
log.Debugf("Full URL to query Envoy Stats Endpoint: %s", envoyStatsUrl)
// Building the client for Envoy server
httpClient, err := client.CreateRetryableHttpClientForEnvoyServer(envoyPrometheusStatsHandler.AgentConfig)
httpClient.HTTPClient.Timeout = EnvoyStatsClientHttpTimeout
if err != nil {
http.Error(resWriter, err.Error(), http.StatusInternalServerError)
return
}
statsRequest, err := client.CreateRetryableAgentRequest(http.MethodGet, envoyStatsUrl, nil)
if err != nil {
http.Error(resWriter, err.Error(), http.StatusInternalServerError)
return
}
// Start a timer to record the response time observed from client side.
start := time.Now()
// Make the request to Envoy stats endpoint
statsResponse, err := httpClient.Do(statsRequest)
duration := time.Since(start)
log.Debugf("Stats request took: %vms", duration.Milliseconds())
if err != nil {
log.Errorf("Call to fetch stats from Envoy admin failed: %s", err)
http.Error(resWriter, "Failed to fetch stats from Envoy", http.StatusInternalServerError)
return
}
if statsResponse.StatusCode != http.StatusOK {
log.Errorf("Envoy stats response status code not OK: %v", statsResponse.Status)
http.Error(resWriter, "Failed to fetch stats from Envoy", http.StatusInternalServerError)
return
}
defer statsResponse.Body.Close()
responseBody, err := ioutil.ReadAll(statsResponse.Body)
if err != nil {
log.Errorf("Failed to read stats response retreived from Envoy admin: %v", err)
http.Error(resWriter, "Failed to fetch stats from Envoy", http.StatusInternalServerError)
return
}
// Directly write the response if there is no filter query
if !envoyPrometheusStatsHandler.QueryParameters.Has(filterQueryKey) {
resWriter.WriteHeader(http.StatusOK)
_, err := resWriter.Write(responseBody)
if err != nil {
log.Errorf("error while writing response: %s", err)
}
return
}
// Filter the stats
filteredMetricFamilies, err := processPrometheusStats(responseBody)
if err != nil {
http.Error(resWriter, fmt.Sprintf("error processing prometheus stats: %s", err), http.StatusInternalServerError)
return
}
resWriter.WriteHeader(http.StatusOK)
err = writeMetricsToResponse(resWriter, filteredMetricFamilies)
if err != nil {
log.Errorf("error while writing response: %s", err)
}
}