summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2018-06-06 12:59:11 +0200
committerMartin Polden <mpolden@mpolden.no>2018-06-06 14:50:04 +0200
commitf4ecceb450096462c846d58bc27c852e4b9a3c37 (patch)
tree7b6e80a5f3f0ec8c5a0707739f03ecda4ed1d956
parent63c83ade591a7b8e43cb9ab593d494e869cf8d88 (diff)
Record deployment activity
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java61
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java32
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java34
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java19
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java64
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java19
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java4
10 files changed, 229 insertions, 82 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index 913adf06f22..795b12f8af9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -79,7 +79,8 @@ public class LockedApplication extends Application {
Deployment newDeployment = new Deployment(zone, applicationVersion, version, instant,
previousDeployment.clusterUtils(),
previousDeployment.clusterInfo(),
- previousDeployment.metrics());
+ previousDeployment.metrics(),
+ previousDeployment.activity());
return with(newDeployment);
}
@@ -96,6 +97,12 @@ public class LockedApplication extends Application {
}
+ public LockedApplication recordActivityAt(Instant instant, ZoneId zone) {
+ Deployment deployment = deployments().get(zone);
+ if (deployment == null) return this;
+ return with(deployment.recordActivityAt(instant));
+ }
+
public LockedApplication with(ZoneId zone, DeploymentMetrics deploymentMetrics) {
Deployment deployment = deployments().get(zone);
if (deployment == null) return this; // No longer deployed in this zone.
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
index 8fa0c6da49c..0a062427a8a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.ClusterSpec.Id;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
import java.time.Instant;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -25,27 +26,25 @@ public class Deployment {
private final Map<Id, ClusterUtilization> clusterUtils;
private final Map<Id, ClusterInfo> clusterInfo;
private final DeploymentMetrics metrics;
+ private final DeploymentActivity activity;
public Deployment(ZoneId zone, ApplicationVersion applicationVersion, Version version, Instant deployTime) {
- this(zone, applicationVersion, version, deployTime, new HashMap<>(), new HashMap<>(), new DeploymentMetrics());
+ this(zone, applicationVersion, version, deployTime, Collections.emptyMap(), Collections.emptyMap(),
+ new DeploymentMetrics(), DeploymentActivity.none);
}
public Deployment(ZoneId zone, ApplicationVersion applicationVersion, Version version, Instant deployTime,
- Map<Id, ClusterUtilization> clusterUtils, Map<Id, ClusterInfo> clusterInfo, DeploymentMetrics metrics) {
- Objects.requireNonNull(zone, "zone cannot be null");
- Objects.requireNonNull(applicationVersion, "applicationVersion cannot be null");
- Objects.requireNonNull(version, "version cannot be null");
- Objects.requireNonNull(deployTime, "deployTime cannot be null");
- Objects.requireNonNull(clusterUtils, "clusterUtils cannot be null");
- Objects.requireNonNull(clusterInfo, "clusterInfo cannot be null");
- Objects.requireNonNull(metrics, "deployment metrics cannot be null");
- this.zone = zone;
- this.applicationVersion = applicationVersion;
- this.version = version;
- this.deployTime = deployTime;
- this.clusterUtils = clusterUtils;
- this.clusterInfo = clusterInfo;
- this.metrics = metrics;
+ Map<Id, ClusterUtilization> clusterUtils, Map<Id, ClusterInfo> clusterInfo,
+ DeploymentMetrics metrics,
+ DeploymentActivity activity) {
+ this.zone = Objects.requireNonNull(zone, "zone cannot be null");
+ this.applicationVersion = Objects.requireNonNull(applicationVersion, "applicationVersion cannot be null");
+ this.version = Objects.requireNonNull(version, "version cannot be null");
+ this.deployTime = Objects.requireNonNull(deployTime, "deployTime cannot be null");
+ this.clusterUtils = Objects.requireNonNull(clusterUtils, "clusterUtils cannot be null");
+ this.clusterInfo = Objects.requireNonNull(clusterInfo, "clusterInfo cannot be null");
+ this.metrics = Objects.requireNonNull(metrics, "deploymentMetrics cannot be null");
+ this.activity = Objects.requireNonNull(activity, "activity cannot be null");
}
/** Returns the zone this was deployed to */
@@ -60,29 +59,42 @@ public class Deployment {
/** Returns the time this was deployed */
public Instant at() { return deployTime; }
+ /** Returns metrics for this */
+ public DeploymentMetrics metrics() {
+ return metrics;
+ }
+
+ /** Returns activity for this */
+ public DeploymentActivity activity() { return activity; }
+
+ /** Returns information about the clusters allocated to this */
public Map<Id, ClusterInfo> clusterInfo() {
return clusterInfo;
}
+ /** Returns utilization of the clusters allocated to this */
public Map<Id, ClusterUtilization> clusterUtils() {
return clusterUtils;
}
+ public Deployment recordActivityAt(Instant instant) {
+ return new Deployment(zone, applicationVersion, version, deployTime, clusterUtils, clusterInfo, metrics,
+ activity.recordAt(instant, metrics));
+ }
+
public Deployment withClusterUtils(Map<Id, ClusterUtilization> clusterUtilization) {
- return new Deployment(zone, applicationVersion, version, deployTime, clusterUtilization, clusterInfo, metrics);
+ return new Deployment(zone, applicationVersion, version, deployTime, clusterUtilization, clusterInfo, metrics,
+ activity);
}
public Deployment withClusterInfo(Map<Id, ClusterInfo> newClusterInfo) {
- return new Deployment(zone, applicationVersion, version, deployTime, clusterUtils, newClusterInfo, metrics);
+ return new Deployment(zone, applicationVersion, version, deployTime, clusterUtils, newClusterInfo, metrics,
+ activity);
}
public Deployment withMetrics(DeploymentMetrics metrics) {
- return new Deployment(zone, applicationVersion, version, deployTime, clusterUtils, clusterInfo, metrics);
- }
-
- /** @return Key metrics for the deployment (application level) like QPS and document count */
- public DeploymentMetrics metrics() {
- return metrics;
+ return new Deployment(zone, applicationVersion, version, deployTime, clusterUtils, clusterInfo, metrics,
+ activity);
}
/**
@@ -109,4 +121,5 @@ public class Deployment {
public String toString() {
return "deployment to " + zone + " of " + applicationVersion + " on version " + version + " at " + deployTime;
}
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java
new file mode 100644
index 00000000000..d4635212e80
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java
@@ -0,0 +1,55 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.application;
+
+import java.time.Instant;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Recent activity in a deployment.
+ *
+ * @author mpolden
+ */
+public class DeploymentActivity {
+
+ /** Query rates at or below this threshold indicate inactivity */
+ private static final double inactivityThreshold = 0;
+
+ public static final DeploymentActivity none = new DeploymentActivity(Optional.empty(), Optional.empty());
+
+ private final Optional<Instant> lastQueried;
+ private final Optional<Instant> lastWritten;
+
+ private DeploymentActivity(Optional<Instant> lastQueried, Optional<Instant> lastWritten) {
+ this.lastQueried = Objects.requireNonNull(lastQueried, "lastQueried must be non-null");
+ this.lastWritten = Objects.requireNonNull(lastWritten, "lastWritten must be non-null");
+ }
+
+ /** The last time this deployment received queries (search) */
+ public Optional<Instant> lastQueried() {
+ return lastQueried;
+ }
+
+ /** The last time this deployment received writes (feed) */
+ public Optional<Instant> lastWritten() {
+ return lastWritten;
+ }
+
+ /** Record activity using given metrics */
+ public DeploymentActivity recordAt(Instant instant, DeploymentMetrics metrics) {
+ return new DeploymentActivity(activityAt(instant, lastQueried, metrics.queriesPerSecond()),
+ activityAt(instant, lastWritten, metrics.writesPerSecond()));
+ }
+
+ public static DeploymentActivity create(Optional<Instant> queriedAt, Optional<Instant> writtenAt) {
+ if (!queriedAt.isPresent() && !writtenAt.isPresent()) {
+ return none;
+ }
+ return new DeploymentActivity(queriedAt, writtenAt);
+ }
+
+ private static Optional<Instant> activityAt(Instant newInstant, Optional<Instant> oldInstant, double rate) {
+ return rate > inactivityThreshold ? Optional.of(newInstant) : oldInstant;
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index 821efba013d..4dacb2e32d6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -15,8 +15,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * Retrieve deployment metrics like qps and document count from the metric service and
- * update the applications with this info.
+ * Retrieve deployment metrics such as QPS and document count from the metric service and
+ * update applications with this info.
*
* @author smorgrav
*/
@@ -39,19 +39,19 @@ public class DeploymentMetricsMaintainer extends Maintainer {
for (Deployment deployment : application.deployments().values()) {
MetricsService.DeploymentMetrics deploymentMetrics = controller().metricsService()
.getDeploymentMetrics(application.id(), deployment.zone());
- DeploymentMetrics appMetrics = new DeploymentMetrics(deploymentMetrics.queriesPerSecond(),
+ DeploymentMetrics newMetrics = new DeploymentMetrics(deploymentMetrics.queriesPerSecond(),
deploymentMetrics.writesPerSecond(),
deploymentMetrics.documentCount(),
deploymentMetrics.queryLatencyMillis(),
deploymentMetrics.writeLatencyMillis());
controller().applications().lockIfPresent(application.id(), lockedApplication ->
- controller().applications().store(lockedApplication.with(deployment.zone(), appMetrics)));
+ controller().applications().store(lockedApplication.with(deployment.zone(), newMetrics)
+ .recordActivityAt(controller().clock().instant(), deployment.zone())));
}
- }
- catch (UncheckedIOException e) {
+ } catch (UncheckedIOException e) {
if (!hasWarned) // produce only one warning per maintenance interval
- log.log(Level.WARNING, "Failed talking to YAMAS: " + Exceptions.toMessageString(e) +
+ log.log(Level.WARNING, "Failed to query metrics service: " + Exceptions.toMessageString(e) +
". Retrying in " + maintenanceInterval());
hasWarned = true;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index 21eea21ba68..6ad2452b2a2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -20,6 +20,7 @@ import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
@@ -68,6 +69,8 @@ public class ApplicationSerializer {
private final String repositoryField = "repositoryField";
private final String branchField = "branchField";
private final String commitField = "commitField";
+ private final String lastQueriedField = "lastQueried";
+ private final String lastWrittenField = "lastWritten";
// DeploymentJobs fields
private final String projectIdField = "projectId";
@@ -148,10 +151,12 @@ public class ApplicationSerializer {
toSlime(deployment.applicationVersion(), object.setObject(applicationPackageRevisionField));
clusterInfoToSlime(deployment.clusterInfo(), object);
clusterUtilsToSlime(deployment.clusterUtils(), object);
- metricsToSlime(deployment.metrics(), object);
+ deploymentMetricsToSlime(deployment.metrics(), object);
+ deployment.activity().lastQueried().ifPresent(instant -> object.setLong(lastQueriedField, instant.toEpochMilli()));
+ deployment.activity().lastWritten().ifPresent(instant -> object.setLong(lastWrittenField, instant.toEpochMilli()));
}
- private void metricsToSlime(DeploymentMetrics metrics, Cursor object) {
+ private void deploymentMetricsToSlime(DeploymentMetrics metrics, Cursor object) {
Cursor root = object.setObject(deploymentMetricsField);
root.setDouble(deploymentMetricsQPSField, metrics.queriesPerSecond());
root.setDouble(deploymentMetricsWPSField, metrics.writesPerSecond());
@@ -289,19 +294,17 @@ public class ApplicationSerializer {
Instant.ofEpochMilli(deploymentObject.field(deployTimeField).asLong()),
clusterUtilsMapFromSlime(deploymentObject.field(clusterUtilsField)),
clusterInfoMapFromSlime(deploymentObject.field(clusterInfoField)),
- deploymentMetricsFromSlime(deploymentObject.field(deploymentMetricsField)));
+ deploymentMetricsFromSlime(deploymentObject.field(deploymentMetricsField)),
+ DeploymentActivity.create(optionalInstant(deploymentObject.field(lastQueriedField)),
+ optionalInstant(deploymentObject.field(lastWrittenField))));
}
private DeploymentMetrics deploymentMetricsFromSlime(Inspector object) {
-
- double queriesPerSecond = object.field(deploymentMetricsQPSField).asDouble();
- double writesPerSecond = object.field(deploymentMetricsWPSField).asDouble();
- double documentCount = object.field(deploymentMetricsDocsField).asDouble();
- double queryLatencyMillis = object.field(deploymentMetricsQueryLatencyField).asDouble();
- double writeLatencyMills = object.field(deploymentMetricsWriteLatencyField).asDouble();
-
- return new DeploymentMetrics(queriesPerSecond, writesPerSecond,
- documentCount, queryLatencyMillis, writeLatencyMills);
+ return new DeploymentMetrics(object.field(deploymentMetricsQPSField).asDouble(),
+ object.field(deploymentMetricsWPSField).asDouble(),
+ object.field(deploymentMetricsDocsField).asDouble(),
+ object.field(deploymentMetricsQueryLatencyField).asDouble(),
+ object.field(deploymentMetricsWriteLatencyField).asDouble());
}
private Map<ClusterSpec.Id, ClusterInfo> clusterInfoMapFromSlime(Inspector object) {
@@ -426,4 +429,9 @@ public class ApplicationSerializer {
return SlimeUtils.optionalString(field);
}
+ private Optional<Instant> optionalInstant(Inspector field) {
+ OptionalLong value = optionalLong(field);
+ return value.isPresent() ? Optional.of(Instant.ofEpochMilli(value.getAsLong())) : Optional.empty();
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 98189613bd0..cf2fa182d0a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -13,7 +13,6 @@ import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
import com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository;
@@ -41,6 +40,7 @@ import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import java.util.Optional;
import java.util.OptionalLong;
+import java.util.function.Supplier;
import java.util.logging.Logger;
import static org.junit.Assert.assertNotNull;
@@ -64,25 +64,28 @@ public final class ControllerTester {
private final ArtifactRepositoryMock artifactRepository;
private final EntityService entityService;
private final MockBuildService buildService;
+ private final MockMetricsService metricsService;
private Controller controller;
- public ControllerTester(ManualClock clock, RotationsConfig rotationsConfig, MockCuratorDb curatorDb) {
+ public ControllerTester(ManualClock clock, RotationsConfig rotationsConfig, MockCuratorDb curatorDb,
+ MockMetricsService metricsService) {
this(new AthenzDbMock(), clock, new ConfigServerMock(new ZoneRegistryMock()),
new ZoneRegistryMock(), new GitHubMock(), curatorDb, rotationsConfig,
- new MemoryNameService(), new ArtifactRepositoryMock(), new MemoryEntityService(), new MockBuildService());
+ new MemoryNameService(), new ArtifactRepositoryMock(), new MemoryEntityService(), new MockBuildService(),
+ metricsService);
}
public ControllerTester(ManualClock clock) {
- this(clock, defaultRotationsConfig(), new MockCuratorDb());
+ this(clock, defaultRotationsConfig(), new MockCuratorDb(), new MockMetricsService());
}
public ControllerTester(RotationsConfig rotationsConfig) {
- this(new ManualClock(), rotationsConfig, new MockCuratorDb());
+ this(new ManualClock(), rotationsConfig, new MockCuratorDb(), new MockMetricsService());
}
public ControllerTester(MockCuratorDb curatorDb) {
- this(new ManualClock(), defaultRotationsConfig(), curatorDb);
+ this(new ManualClock(), defaultRotationsConfig(), curatorDb, new MockMetricsService());
}
public ControllerTester() {
@@ -93,7 +96,8 @@ public final class ControllerTester {
ConfigServerMock configServer, ZoneRegistryMock zoneRegistry,
GitHubMock gitHub, CuratorDb curator, RotationsConfig rotationsConfig,
MemoryNameService nameService, ArtifactRepositoryMock artifactRepository,
- EntityService entityService, MockBuildService buildService) {
+ EntityService entityService, MockBuildService buildService,
+ MockMetricsService metricsService) {
this.athenzDb = athenzDb;
this.clock = clock;
this.configServer = configServer;
@@ -105,8 +109,10 @@ public final class ControllerTester {
this.artifactRepository = artifactRepository;
this.entityService = entityService;
this.buildService = buildService;
+ this.metricsService = metricsService;
this.controller = createController(curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry,
- athenzDb, nameService, artifactRepository, entityService, buildService);
+ athenzDb, nameService, artifactRepository, entityService, buildService,
+ metricsService);
// Make root logger use time from manual clock
Logger.getLogger("").getHandlers()[0].setFilter(
@@ -138,10 +144,12 @@ public final class ControllerTester {
public MockBuildService buildService() { return buildService; }
+ public MockMetricsService metricsService() { return metricsService; }
+
/** Create a new controller instance. Useful to verify that controller state is rebuilt from persistence */
public final void createNewController() {
controller = createController(curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry, athenzDb,
- nameService, artifactRepository, entityService, buildService);
+ nameService, artifactRepository, entityService, buildService, metricsService);
}
/** Creates the given tenant and application and deploys it */
@@ -241,6 +249,10 @@ public final class ControllerTester {
new DeployOptions(false, Optional.empty(), false, deployCurrentVersion));
}
+ public Supplier<Application> application(ApplicationId application) {
+ return () -> controller().applications().require(application);
+ }
+
/** Used by ApplicationSerializerTest to avoid breaking encapsulation. Should not be used by anything else */
public static LockedApplication writable(Application application) {
return new LockedApplication(application, new Lock("/test", new MockCurator()));
@@ -251,7 +263,7 @@ public final class ControllerTester {
GitHubMock gitHub, ZoneRegistryMock zoneRegistryMock,
AthenzDbMock athensDb, MemoryNameService nameService,
ArtifactRepository artifactRepository, EntityService entityService,
- BuildService buildService) {
+ BuildService buildService, MockMetricsService metricsService) {
Controller controller = new Controller(curator,
rotationsConfig,
gitHub,
@@ -260,7 +272,7 @@ public final class ControllerTester {
new MemoryGlobalRoutingService(),
zoneRegistryMock,
configServer,
- new MockMetricsService(),
+ metricsService,
nameService,
new MockRoutingGenerator(),
new ChefMock(),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java
index 88bbb582564..67a4139ecf1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MockMetricsService.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.integration;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
import java.util.HashMap;
@@ -10,16 +11,28 @@ import java.util.Map;
/**
* @author bratseth
*/
-public class MockMetricsService implements com.yahoo.vespa.hosted.controller.api.integration.MetricsService {
+public class MockMetricsService implements MetricsService {
+
+ private final Map<String, Double> metrics = new HashMap<>();
+
+ public MockMetricsService setMetric(String key, Double value) {
+ metrics.put(key, value);
+ return this;
+ }
@Override
public ApplicationMetrics getApplicationMetrics(ApplicationId application) {
- return new ApplicationMetrics(0.5, 0.7);
+ return new ApplicationMetrics(metrics.getOrDefault("queryServiceQuality", 0.5),
+ metrics.getOrDefault("writeServiceQuality", 0.7));
}
@Override
public DeploymentMetrics getDeploymentMetrics(ApplicationId application, ZoneId zone) {
- return new DeploymentMetrics(1, 2, 3, 4, 5);
+ return new DeploymentMetrics(metrics.getOrDefault("queriesPerSecond", 1D),
+ metrics.getOrDefault("writesPerSecond", 2D),
+ metrics.getOrDefault("docoumentCount", 3D).longValue(),
+ metrics.getOrDefault("queryLatencyMillis", 4D),
+ metrics.getOrDefault("writeLatencyMillis", 5D));
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
index 148d11e8b38..a651210767d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
@@ -12,37 +12,69 @@ import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import org.junit.Test;
import java.time.Duration;
+import java.time.Instant;
+import java.util.function.Supplier;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
/**
* @author smorgrav
+ * @author mpolden
*/
public class DeploymentMetricsMaintainerTest {
@Test
public void maintain() {
ControllerTester tester = new ControllerTester();
- ApplicationId app = tester.createAndDeploy("tenant1", "domain1", "app1", Environment.dev, 123).id();
+ ApplicationId appId = tester.createAndDeploy("tenant1", "domain1", "app1",
+ Environment.dev, 123).id();
+ DeploymentMetricsMaintainer maintainer = new DeploymentMetricsMaintainer(tester.controller(),
+ Duration.ofDays(1),
+ new JobControl(new MockCuratorDb()));
+ Supplier<Application> app = tester.application(appId);
+ Supplier<Deployment> deployment = () -> app.get().deployments().values().stream().findFirst().get();
- // Pre condition: no metric info on neither application nor deployment
- assertEquals(0, tester.controller().applications().require(app).metrics().queryServiceQuality(), 0);
- Deployment deployment = tester.controller().applications().get(app).get().deployments().values().stream().findAny().get();
- assertEquals(0, deployment.metrics().documentCount(), 0);
+ // No metrics gathered yet
+ assertEquals(0, app.get().metrics().queryServiceQuality(), 0);
+ assertEquals(0, deployment.get().metrics().documentCount(), 0);
+ assertFalse("Never received any queries", deployment.get().activity().lastQueried().isPresent());
+ assertFalse("Never received any writes", deployment.get().activity().lastWritten().isPresent());
- DeploymentMetricsMaintainer maintainer = new DeploymentMetricsMaintainer(tester.controller(), Duration.ofMinutes(10), new JobControl(new MockCuratorDb()));
+ // Metrics are gathered and saved to application
maintainer.maintain();
+ assertEquals(0.5, app.get().metrics().queryServiceQuality(), Double.MIN_VALUE);
+ assertEquals(0.7, app.get().metrics().writeServiceQuality(), Double.MIN_VALUE);
+ assertEquals(1, deployment.get().metrics().queriesPerSecond(), Double.MIN_VALUE);
+ assertEquals(2, deployment.get().metrics().writesPerSecond(), Double.MIN_VALUE);
+ assertEquals(3, deployment.get().metrics().documentCount(), Double.MIN_VALUE);
+ assertEquals(4, deployment.get().metrics().queryLatencyMillis(), Double.MIN_VALUE);
+ assertEquals(5, deployment.get().metrics().writeLatencyMillis(), Double.MIN_VALUE);
+ Instant t1 = tester.clock().instant();
+ assertEquals(t1, deployment.get().activity().lastQueried().get());
+ assertEquals(t1, deployment.get().activity().lastWritten().get());
- // Post condition:
- Application application = tester.controller().applications().require(app);
- assertEquals(0.5, application.metrics().queryServiceQuality(), Double.MIN_VALUE);
- assertEquals(0.7, application.metrics().writeServiceQuality(), Double.MIN_VALUE);
- deployment = application.deployments().values().stream().findAny().get();
- assertEquals(1, deployment.metrics().queriesPerSecond(), Double.MIN_VALUE);
- assertEquals(2, deployment.metrics().writesPerSecond(), Double.MIN_VALUE);
- assertEquals(3, deployment.metrics().documentCount(), Double.MIN_VALUE);
- assertEquals(4, deployment.metrics().queryLatencyMillis(), Double.MIN_VALUE);
- assertEquals(5, deployment.metrics().writeLatencyMillis(), Double.MIN_VALUE);
+ // Time passes. Activity is updated as app is still receiving traffic
+ tester.clock().advance(Duration.ofHours(1));
+ Instant t2 = tester.clock().instant();
+ maintainer.maintain();
+ assertEquals(t2, deployment.get().activity().lastQueried().get());
+ assertEquals(t2, deployment.get().activity().lastWritten().get());
+
+ // Query traffic disappears. Query activity time is no longer updated
+ tester.clock().advance(Duration.ofHours(1));
+ Instant t3 = tester.clock().instant();
+ tester.metricsService().setMetric("queriesPerSecond", 0D);
+ maintainer.maintain();
+ assertEquals(t2, deployment.get().activity().lastQueried().get());
+ assertEquals(t3, deployment.get().activity().lastWritten().get());
+
+ // Feed traffic disappears. Feed activity time is no longer updated
+ tester.clock().advance(Duration.ofHours(1));
+ tester.metricsService().setMetric("writesPerSecond", 0D);
+ maintainer.maintain();
+ assertEquals(t2, deployment.get().activity().lastQueried().get());
+ assertEquals(t3, deployment.get().activity().lastWritten().get());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 071ec7b5c11..26cb68647e4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
@@ -65,9 +66,12 @@ public class ApplicationSerializerTest {
ApplicationVersion applicationVersion1 = ApplicationVersion.from(new SourceRevision("repo1", "branch1", "commit1"), 31);
ApplicationVersion applicationVersion2 = ApplicationVersion
.from(new SourceRevision("repo1", "branch1", "commit1"), 32);
+ Instant activityAt = Instant.parse("2018-06-01T10:15:30.00Z");
deployments.add(new Deployment(zone1, applicationVersion1, Version.fromString("1.2.3"), Instant.ofEpochMilli(3))); // One deployment without cluster info and utils
deployments.add(new Deployment(zone2, applicationVersion2, Version.fromString("1.2.3"), Instant.ofEpochMilli(5),
- createClusterUtils(3, 0.2), createClusterInfo(3, 4),new DeploymentMetrics(2,3,4,5,6)));
+ createClusterUtils(3, 0.2), createClusterInfo(3, 4),
+ new DeploymentMetrics(2,3,4,5,6),
+ DeploymentActivity.create(Optional.of(activityAt), Optional.of(activityAt))));
OptionalLong projectId = OptionalLong.of(123L);
List<JobStatus> statusList = new ArrayList<>();
@@ -108,6 +112,8 @@ public class ApplicationSerializerTest {
assertEquals(original.deployments().get(zone2).version(), serialized.deployments().get(zone2).version());
assertEquals(original.deployments().get(zone1).at(), serialized.deployments().get(zone1).at());
assertEquals(original.deployments().get(zone2).at(), serialized.deployments().get(zone2).at());
+ assertEquals(original.deployments().get(zone2).activity().lastQueried().get(), serialized.deployments().get(zone2).activity().lastQueried().get());
+ assertEquals(original.deployments().get(zone2).activity().lastWritten().get(), serialized.deployments().get(zone2).activity().lastWritten().get());
assertEquals(original.deploymentJobs().projectId(), serialized.deploymentJobs().projectId());
assertEquals(original.deploymentJobs().jobStatus().size(), serialized.deploymentJobs().jobStatus().size());
@@ -143,12 +149,11 @@ public class ApplicationSerializerTest {
// Test metrics
assertEquals(original.metrics().queryServiceQuality(), serialized.metrics().queryServiceQuality(), Double.MIN_VALUE);
assertEquals(original.metrics().writeServiceQuality(), serialized.metrics().writeServiceQuality(), Double.MIN_VALUE);
-
- assertEquals(2, serialized.deployments().get(zone2).metrics().queriesPerSecond(), Double.MIN_VALUE);
- assertEquals(3, serialized.deployments().get(zone2).metrics().writesPerSecond(), Double.MIN_VALUE);
- assertEquals(4, serialized.deployments().get(zone2).metrics().documentCount(), Double.MIN_VALUE);
- assertEquals(5, serialized.deployments().get(zone2).metrics().queryLatencyMillis(), Double.MIN_VALUE);
- assertEquals(6, serialized.deployments().get(zone2).metrics().writeLatencyMillis(), Double.MIN_VALUE);
+ assertEquals(original.deployments().get(zone2).metrics().queriesPerSecond(), serialized.deployments().get(zone2).metrics().queriesPerSecond(), Double.MIN_VALUE);
+ assertEquals(original.deployments().get(zone2).metrics().writesPerSecond(), serialized.deployments().get(zone2).metrics().writesPerSecond(), Double.MIN_VALUE);
+ assertEquals(original.deployments().get(zone2).metrics().documentCount(), serialized.deployments().get(zone2).metrics().documentCount(), Double.MIN_VALUE);
+ assertEquals(original.deployments().get(zone2).metrics().queryLatencyMillis(), serialized.deployments().get(zone2).metrics().queryLatencyMillis(), Double.MIN_VALUE);
+ assertEquals(original.deployments().get(zone2).metrics().writeLatencyMillis(), serialized.deployments().get(zone2).metrics().writeLatencyMillis(), Double.MIN_VALUE);
{ // test more deployment serialization cases
Application original2 = writable(original).withChange(Change.of(ApplicationVersion.from(new SourceRevision("repo1", "branch1", "commit1"), 42)));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 8d734ec549c..545ee529635 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -57,6 +57,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -1123,7 +1124,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
lockedApplication = lockedApplication
.withClusterInfo(deployment.zone(), clusterInfo)
.withClusterUtilization(deployment.zone(), clusterUtils)
- .with(deployment.zone(), metrics);
+ .with(deployment.zone(), metrics)
+ .recordActivityAt(Instant.parse("2018-06-01T10:15:30.00Z"), deployment.zone());
}
controllerTester.controller().applications().store(lockedApplication);
});