aboutsummaryrefslogtreecommitdiffstats
path: root/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/application/NodeMetricsClientTest.java
blob: 420d37fc32b0ed43271bf176764cca574e77b328 (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
146
147
148
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.metricsproxy.http.application;

import ai.vespa.metricsproxy.http.metrics.MetricsV1Handler;
import ai.vespa.metricsproxy.metric.model.ConsumerId;
import ai.vespa.metricsproxy.metric.model.MetricsPacket;
import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
import com.yahoo.test.ManualClock;

import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;

import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.ExecutionException;

import static ai.vespa.metricsproxy.TestUtil.getFileContents;
import static ai.vespa.metricsproxy.http.ValuesFetcher.defaultMetricsConsumerId;
import static ai.vespa.metricsproxy.metric.model.ConsumerId.toConsumerId;
import static ai.vespa.metricsproxy.metric.model.MetricId.toMetricId;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Two optimizations worth noting:
 * <p>
 * 1. Using a ClassRule for the wire mocking means it is reused between test methods.
 * 2. Configuring stubs on the rule is faster than using the static WireMock.stubFor method.
 *
 * @author gjoranv
 */
public class NodeMetricsClientTest {

    private static final String TEST_FILE = "generic-sample.json";
    private static final String RESPONSE = getFileContents(TEST_FILE);
    private static final CloseableHttpAsyncClient httpClient = ApplicationMetricsRetriever.createHttpClient();

    private static final String CPU_METRIC = "cpu.util";
    private static final String REPLACED_CPU_METRIC = "replaced_cpu_util";
    private static final String CUSTOM_CONSUMER = "custom-consumer";
    private static final Duration TTL = Duration.ofSeconds(30);


    private static Node node;

    private NodeMetricsClient nodeMetricsClient;

    @ClassRule
    public static WireMockClassRule wireMockRule = new WireMockClassRule(options().dynamicPort());

    @BeforeClass
    public static void setupWireMock() {
        node = new Node("id", "localhost", wireMockRule.port(), MetricsV1Handler.VALUES_PATH);
        URI metricsUri = node.metricsUri(defaultMetricsConsumerId);
        wireMockRule.stubFor(get(urlPathEqualTo(metricsUri.getPath()))
                                     .willReturn(aResponse().withBody(RESPONSE)));

        wireMockRule.stubFor(get(urlPathEqualTo(metricsUri.getPath()))
                                     .withQueryParam("consumer", equalTo(defaultMetricsConsumerId.id))
                                     .willReturn(aResponse().withBody(RESPONSE)));

        // Add a slightly different response for a custom consumer.
        String myConsumerResponse = RESPONSE.replaceAll(CPU_METRIC, REPLACED_CPU_METRIC);
        wireMockRule.stubFor(get(urlPathEqualTo(metricsUri.getPath()))
                                     .withQueryParam("consumer", equalTo(CUSTOM_CONSUMER))
                                     .willReturn(aResponse().withBody(myConsumerResponse)));

    }

    @Before
    public void setupClient() {
        httpClient.start();
        nodeMetricsClient = new NodeMetricsClient(httpClient, node, new ManualClock());
    }

    @Test
    public void metrics_are_not_retrieved_until_first_request() {
        assertEquals(0, nodeMetricsClient.snapshotsRetrieved());
    }

    @Test
    public void metrics_are_retrieved_upon_first_update() throws InterruptedException, ExecutionException {
        assertEquals(0, nodeMetricsClient.getMetrics(defaultMetricsConsumerId).size());
        assertEquals(0, nodeMetricsClient.snapshotsRetrieved());
        updateSnapshot(defaultMetricsConsumerId, TTL);
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
        List<MetricsPacket> metrics = nodeMetricsClient.getMetrics(defaultMetricsConsumerId);
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
        assertEquals(4, metrics.size());
   }

    @Test
    public void metrics_are_refreshed_on_every_update() {
        assertEquals(0, nodeMetricsClient.snapshotsRetrieved());
        updateSnapshot(defaultMetricsConsumerId, TTL);
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
        updateSnapshot(defaultMetricsConsumerId, Duration.ZERO);
        assertEquals(2, nodeMetricsClient.snapshotsRetrieved());
    }

    @Test
    public void metrics_are_not_refreshed_if_ttl_not_expired() {
        assertEquals(0, nodeMetricsClient.snapshotsRetrieved());
        updateSnapshot(defaultMetricsConsumerId, TTL);
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
        updateSnapshot(defaultMetricsConsumerId, TTL);
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
        updateSnapshot(defaultMetricsConsumerId, Duration.ZERO);
        assertEquals(2, nodeMetricsClient.snapshotsRetrieved());
    }

    @Test
    public void metrics_for_different_consumers_are_cached_separately() {
        updateSnapshot(defaultMetricsConsumerId, TTL);
        List<MetricsPacket> defaultMetrics = nodeMetricsClient.getMetrics(defaultMetricsConsumerId);
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
        assertEquals(4, defaultMetrics.size());

        updateSnapshot(toConsumerId(CUSTOM_CONSUMER), TTL);
        List<MetricsPacket> customMetrics = nodeMetricsClient.getMetrics(toConsumerId(CUSTOM_CONSUMER));
        assertEquals(2, nodeMetricsClient.snapshotsRetrieved());
        assertEquals(4, customMetrics.size());

        MetricsPacket replacedCpuMetric = customMetrics.get(0);
        assertTrue(replacedCpuMetric.metrics().containsKey(toMetricId(REPLACED_CPU_METRIC)));
    }

    private void updateSnapshot(ConsumerId consumerId, Duration ttl) {
        var optional = nodeMetricsClient.startSnapshotUpdate(consumerId, ttl);
        optional.ifPresent(future -> {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new AssertionError(e);
            }
        });
    }

}