summaryrefslogtreecommitdiffstats
path: root/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsV2MetricsFetcher.java
blob: 8f06a7bb30a138be684fc57cc533f433dc9535dd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.autoscale;

import ai.vespa.util.http.hc5.VespaAsyncHttpClientBuilder;
import com.yahoo.component.annotation.Inject;
import com.yahoo.component.AbstractComponent;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.orchestrator.HostNameNotFoundException;
import com.yahoo.vespa.orchestrator.Orchestrator;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.core5.concurrent.FutureCallback;

import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Fetches node metrics over the metrics/v2 API
 *
 * @author bratseth
 */
public class MetricsV2MetricsFetcher extends AbstractComponent implements MetricsFetcher {

    private static final Logger log = Logger.getLogger(MetricsV2MetricsFetcher.class.getName());

    private static final String apiPath = "/metrics/v2/values";

    private final NodeRepository nodeRepository;
    private final Orchestrator orchestrator;
    private final AsyncHttpClient httpClient;

    @Inject
    @SuppressWarnings("unused")
    public MetricsV2MetricsFetcher(NodeRepository nodeRepository, Orchestrator orchestrator) {
        this(nodeRepository, orchestrator, new AsyncApacheHttpClient());
    }

    public MetricsV2MetricsFetcher(NodeRepository nodeRepository, Orchestrator orchestrator, AsyncHttpClient httpClient) {
        this.nodeRepository = nodeRepository;
        this.orchestrator = orchestrator;
        this.httpClient = httpClient;
    }

    @Override
    public CompletableFuture<MetricsResponse> fetchMetrics(ApplicationId application) {
        NodeList applicationNodes = nodeRepository.nodes().list().owner(application).state(Node.State.active);

        Optional<Node> metricsV2Container = applicationNodes.container()
                                                            .matching(node -> expectedUp(node))
                                                            .first();
        if (metricsV2Container.isEmpty()) {
            return CompletableFuture.completedFuture(MetricsResponse.empty());
        }
        else {
            // Consumer 'autoscaling' defined in com.yahoo.vespa.model.admin.monitoring.MetricConsumer
            String url = "http://" + metricsV2Container.get().hostname() + ":" + 4080 + apiPath + "?consumer=autoscaling";
            return httpClient.get(url)
                             .thenApply(response -> new MetricsResponse(response, applicationNodes, nodeRepository));
        }
    }

    @Override
    public void deconstruct() {
        httpClient.close();
    }

    private boolean expectedUp(Node node) {
        try {
            return ! orchestrator.getNodeStatus(new HostName(node.hostname())).isSuspended();
        }
        catch (HostNameNotFoundException e) {
            return false;
        }
    }

    /** A simple async HTTP client */
    public interface AsyncHttpClient {

        CompletableFuture<String> get(String url);
        void close();

    }

    /** Implements the AsyncHttpClient interface by delegating to an Apache HTTP client */
    public static class AsyncApacheHttpClient implements AsyncHttpClient {

        private final CloseableHttpAsyncClient httpClient = VespaAsyncHttpClientBuilder.create().build();

        public AsyncApacheHttpClient() {
            httpClient.start();
        }

        @Override
        public CompletableFuture<String> get(String url) {
            CompletableFuture<String> callback = new CompletableFuture<>();
            httpClient.execute(new SimpleHttpRequest("GET", url), new CallbackAdaptor(callback));
            return callback;
        }

        @Override
        public void close() {
            try {
                httpClient.close();
            }
            catch (IOException e) {
                log.log(Level.WARNING, "Exception deconstructing", e);
            }
        }

        private static class CallbackAdaptor implements FutureCallback<SimpleHttpResponse> {

            private final CompletableFuture<String> callback;

            public CallbackAdaptor(CompletableFuture<String> callback) {
                this.callback = callback;
            }

            @Override
            public void completed(SimpleHttpResponse simpleHttpResponse) {
                callback.complete(simpleHttpResponse.getBodyText());
            }

            @Override
            public void failed(Exception e) {
                callback.completeExceptionally(e);
            }

            @Override
            public void cancelled() {
                callback.cancel(true);
            }

        }

    }

}