diff options
14 files changed, 111 insertions, 60 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java index 5b1faa7218a..c91c4e92486 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java @@ -704,6 +704,10 @@ public class VespaMetricSet { metrics.add(new Metric("vds.idealstate.buckets_toomanycopies.average")); metrics.add(new Metric("vds.idealstate.buckets.average")); metrics.add(new Metric("vds.idealstate.buckets_notrusted.average")); + metrics.add(new Metric("vds.idealstate.bucket_replicas_moving_out.average")); + metrics.add(new Metric("vds.idealstate.bucket_replicas_copying_out.average")); + metrics.add(new Metric("vds.idealstate.bucket_replicas_copying_in.average")); + metrics.add(new Metric("vds.idealstate.bucket_replicas_syncing.average")); metrics.add(new Metric("vds.idealstate.delete_bucket.done_ok.rate")); metrics.add(new Metric("vds.idealstate.delete_bucket.done_failed.rate")); metrics.add(new Metric("vds.idealstate.delete_bucket.pending.average")); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java index d0c08d2747d..fd27dedf041 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java @@ -10,7 +10,8 @@ import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.ProvisionLogger; import com.yahoo.config.provision.Provisioner; -import java.util.*; +import java.util.List; + /** * A wrapper for {@link Provisioner} to avoid having to expose multitenant diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 14b5da1edd3..87736d8e591 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -140,12 +140,6 @@ public class Flags { "Only allow modifications of disks by disk task in limited situations.", "Takes effect on next host-admin tick."); - public static final UnboundBooleanFlag NEW_SPARE_DISKS = defineFeatureFlag( - "new-spare-disks", true, - List.of("hakonhall"), "2021-09-08", "2021-11-08", - "Use a new algorithm to calculate the spare disks of a host.", - "Takes effect on first run of DiskTask, typically after host-admin restart/upgrade."); - public static final UnboundBooleanFlag LOCAL_SUSPEND = defineFeatureFlag( "local-suspend", false, List.of("hakonhall"), "2021-09-21", "2021-10-21", diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java index 487c60e0dcb..000b6cd4149 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java @@ -1,4 +1,4 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.hosted.api; import com.yahoo.config.provision.ApplicationId; @@ -340,6 +340,8 @@ public abstract class ControllerHttpClient { Slime slime = new Slime(); Cursor rootObject = slime.setObject(); deployment.version().ifPresent(version -> rootObject.setString("vespaVersion", version)); + if (deployment.isDryRun()) + rootObject.setString("dryRun", "true"); return toJson(slime); } diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/Deployment.java b/hosted-api/src/main/java/ai/vespa/hosted/api/Deployment.java index d012d27fbd8..77cc116b413 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/Deployment.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/Deployment.java @@ -1,9 +1,8 @@ -// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.hosted.api; import java.nio.file.Path; import java.util.Optional; -import java.util.OptionalLong; /** * A deployment intended for hosted Vespa, containing an application package and some meta data. @@ -12,23 +11,31 @@ public class Deployment { private final Optional<String> version; private final Path applicationZip; + private final boolean dryRun; - private Deployment(Optional<String> version, Path applicationZip) { + private Deployment(Optional<String> version, Path applicationZip, boolean dryRun) { this.version = version; this.applicationZip = applicationZip; + this.dryRun = dryRun; } /** Returns a deployment which will use the provided application package. */ public static Deployment ofPackage(Path applicationZipFile) { - return new Deployment(Optional.empty(), applicationZipFile); + return new Deployment(Optional.empty(), applicationZipFile, false); } /** Returns a copy of this which will have the specified Vespa version on its nodes. */ public Deployment atVersion(String vespaVersion) { - return new Deployment(Optional.of(vespaVersion), applicationZip); + return new Deployment(Optional.of(vespaVersion), applicationZip, false); + } + + /** Returns a copy of this which will do a dry-run deployment. */ + public Deployment withDryRun() { + return new Deployment(version, applicationZip, true); } public Optional<String> version() { return version; } public Path applicationZip() { return applicationZip; } + public boolean isDryRun() { return dryRun; } } diff --git a/storage/src/tests/distributor/distributor_stripe_test.cpp b/storage/src/tests/distributor/distributor_stripe_test.cpp index a38b40d3682..067b0efdd5c 100644 --- a/storage/src/tests/distributor/distributor_stripe_test.cpp +++ b/storage/src/tests/distributor/distributor_stripe_test.cpp @@ -475,7 +475,7 @@ TEST_F(DistributorStripeTest, merge_stats_are_accumulated_during_database_iterat // added to existing. tickDistributorNTimes(50); - const auto& stats = stripe_maintenance_stats(); + const auto stats = stripe_maintenance_stats(); { NodeMaintenanceStats wanted; wanted.syncing = 1; @@ -501,6 +501,11 @@ TEST_F(DistributorStripeTest, merge_stats_are_accumulated_during_database_iterat assertBucketSpaceStats(1, 3, 0, "default", bucketStats); assertBucketSpaceStats(0, 1, 1, "default", bucketStats); assertBucketSpaceStats(3, 1, 2, "default", bucketStats); + + EXPECT_EQ(stats.perNodeStats.total_replica_stats().movingOut, 1); + EXPECT_EQ(stats.perNodeStats.total_replica_stats().copyingOut, 2); + EXPECT_EQ(stats.perNodeStats.total_replica_stats().copyingIn, 2); + EXPECT_EQ(stats.perNodeStats.total_replica_stats().syncing, 2); } void @@ -534,7 +539,7 @@ TEST_F(DistributorStripeTest, stats_generated_for_preempted_operations) // by activation, we'll see no merge stats at all. addNodesToBucketDB(document::BucketId(16, 1), "0=1/1/1,1=2/2/2"); tickDistributorNTimes(50); - const auto& stats = stripe_maintenance_stats(); + const auto stats = stripe_maintenance_stats(); { NodeMaintenanceStats wanted; wanted.syncing = 1; diff --git a/storage/src/vespa/storage/distributor/distributor_stripe.cpp b/storage/src/vespa/storage/distributor/distributor_stripe.cpp index 21059649ef9..da32b7ad4c6 100644 --- a/storage/src/vespa/storage/distributor/distributor_stripe.cpp +++ b/storage/src/vespa/storage/distributor/distributor_stripe.cpp @@ -558,8 +558,14 @@ DistributorStripe::propagateInternalScanMetricsToExternal() // All shared values are written when _metricLock is held, so no races. if (_bucketDBMetricUpdater.hasCompletedRound()) { - _bucketDbStats.propagateMetrics(_idealStateManager.getMetrics(), getMetrics()); - _idealStateManager.getMetrics().setPendingOperations(_maintenanceStats.global.pending); + auto& ideal_state_metrics = _idealStateManager.getMetrics(); + _bucketDbStats.propagateMetrics(ideal_state_metrics, getMetrics()); + ideal_state_metrics.setPendingOperations(_maintenanceStats.global.pending); + const auto& total_stats = _maintenanceStats.perNodeStats.total_replica_stats(); + ideal_state_metrics.buckets_replicas_moving_out.set(total_stats.movingOut); + ideal_state_metrics.buckets_replicas_copying_out.set(total_stats.copyingOut); + ideal_state_metrics.buckets_replicas_copying_in.set(total_stats.copyingIn); + ideal_state_metrics.buckets_replicas_syncing.set(total_stats.syncing); } } diff --git a/storage/src/vespa/storage/distributor/idealstatemanager.cpp b/storage/src/vespa/storage/distributor/idealstatemanager.cpp index 124f75ec169..f1d2b163623 100644 --- a/storage/src/vespa/storage/distributor/idealstatemanager.cpp +++ b/storage/src/vespa/storage/distributor/idealstatemanager.cpp @@ -21,8 +21,7 @@ using document::BucketSpace; using storage::lib::Node; using storage::lib::NodeType; -namespace storage { -namespace distributor { +namespace storage::distributor { IdealStateManager::IdealStateManager( const DistributorNodeContext& node_ctx, @@ -298,5 +297,4 @@ void IdealStateManager::getBucketStatus(std::ostream& out) const { } } -} // distributor -} // storage +} // storage::distributor diff --git a/storage/src/vespa/storage/distributor/idealstatemetricsset.cpp b/storage/src/vespa/storage/distributor/idealstatemetricsset.cpp index fd193ad6fd8..e786d81df91 100644 --- a/storage/src/vespa/storage/distributor/idealstatemetricsset.cpp +++ b/storage/src/vespa/storage/distributor/idealstatemetricsset.cpp @@ -86,7 +86,21 @@ IdealStateMetricSet::IdealStateMetricSet() {{"logdefault"},{"yamasdefault"}}, "The number of buckets that we are rechecking for " "ideal state operations", this), - startOperationsLatency("start_operations_latency", {}, "Time used in startOperations()", this), + buckets_replicas_moving_out("bucket_replicas_moving_out", + {{"logdefault"},{"yamasdefault"}}, + "Bucket replicas that should be moved out, e.g. retirement case or node " + "added to cluster that has higher ideal state priority.", this), + buckets_replicas_copying_in("bucket_replicas_copying_in", + {{"logdefault"},{"yamasdefault"}}, + "Bucket replicas that should be copied in, e.g. node does not have a " + "replica for a bucket that it is in ideal state for", this), + buckets_replicas_copying_out("bucket_replicas_copying_out", + {{"logdefault"},{"yamasdefault"}}, + "Bucket replicas that should be copied out, e.g. node is in ideal state " + "but might have to provide data other nodes in a merge", this), + buckets_replicas_syncing("bucket_replicas_syncing", + {{"logdefault"},{"yamasdefault"}}, + "Bucket replicas that need syncing due to mismatching metadata", this), nodesPerMerge("nodes_per_merge", {}, "The number of nodes involved in a single merge operation.", this) { createOperationMetrics(); diff --git a/storage/src/vespa/storage/distributor/idealstatemetricsset.h b/storage/src/vespa/storage/distributor/idealstatemetricsset.h index c1fb39bb50a..e9ccef9f93e 100644 --- a/storage/src/vespa/storage/distributor/idealstatemetricsset.h +++ b/storage/src/vespa/storage/distributor/idealstatemetricsset.h @@ -38,8 +38,11 @@ public: metrics::LongValueMetric buckets_toomanycopies; metrics::LongValueMetric buckets; metrics::LongValueMetric buckets_notrusted; - metrics::LongValueMetric buckets_rechecking; - metrics::LongAverageMetric startOperationsLatency; + metrics::LongValueMetric buckets_rechecking; // TODO remove, not used (but exposed by VespaMetricSet) + metrics::LongValueMetric buckets_replicas_moving_out; + metrics::LongValueMetric buckets_replicas_copying_in; + metrics::LongValueMetric buckets_replicas_copying_out; + metrics::LongValueMetric buckets_replicas_syncing; metrics::DoubleAverageMetric nodesPerMerge; void createOperationMetrics(); diff --git a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp index 4e7f7d9d89d..db2eb6aadc9 100644 --- a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp +++ b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.cpp @@ -34,9 +34,9 @@ merge_bucket_spaces_stats(NodeMaintenanceStatsTracker::BucketSpacesStats& dest, void NodeMaintenanceStatsTracker::merge(const NodeMaintenanceStatsTracker& rhs) { - for (const auto& entry : rhs._stats) { + for (const auto& entry : rhs._node_stats) { auto node_index = entry.first; - merge_bucket_spaces_stats(_stats[node_index], entry.second); + merge_bucket_spaces_stats(_node_stats[node_index], entry.second); } } diff --git a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h index 6399e53089b..3c45bcdd5e5 100644 --- a/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h +++ b/storage/src/vespa/storage/distributor/maintenance/node_maintenance_stats_tracker.h @@ -50,7 +50,8 @@ public: using PerNodeStats = std::unordered_map<uint16_t, BucketSpacesStats>; private: - PerNodeStats _stats; + PerNodeStats _node_stats; + NodeMaintenanceStats _total_stats; static const NodeMaintenanceStats _emptyNodeMaintenanceStats; public: @@ -58,23 +59,28 @@ public: ~NodeMaintenanceStatsTracker(); void incMovingOut(uint16_t node, document::BucketSpace bucketSpace) { - ++_stats[node][bucketSpace].movingOut; + ++_node_stats[node][bucketSpace].movingOut; + ++_total_stats.movingOut; } void incSyncing(uint16_t node, document::BucketSpace bucketSpace) { - ++_stats[node][bucketSpace].syncing; + ++_node_stats[node][bucketSpace].syncing; + ++_total_stats.syncing; } void incCopyingIn(uint16_t node, document::BucketSpace bucketSpace) { - ++_stats[node][bucketSpace].copyingIn; + ++_node_stats[node][bucketSpace].copyingIn; + ++_total_stats.copyingIn; } void incCopyingOut(uint16_t node, document::BucketSpace bucketSpace) { - ++_stats[node][bucketSpace].copyingOut; + ++_node_stats[node][bucketSpace].copyingOut; + ++_total_stats.copyingOut; } void incTotal(uint16_t node, document::BucketSpace bucketSpace) { - ++_stats[node][bucketSpace].total; + ++_node_stats[node][bucketSpace].total; + ++_total_stats.total; } /** @@ -82,8 +88,8 @@ public: * if none have been recorded yet */ const NodeMaintenanceStats& forNode(uint16_t node, document::BucketSpace bucketSpace) const { - auto nodeItr = _stats.find(node); - if (nodeItr != _stats.end()) { + auto nodeItr = _node_stats.find(node); + if (nodeItr != _node_stats.end()) { auto bucketSpaceItr = nodeItr->second.find(bucketSpace); if (bucketSpaceItr != nodeItr->second.end()) { return bucketSpaceItr->second; @@ -93,11 +99,18 @@ public: } const PerNodeStats& perNodeStats() const { - return _stats; + return _node_stats; + } + + // Note: the total statistics are across all replicas across all buckets across all bucket spaces. + // That means it's possible for a single bucket to count more than once, up to once per replica. + // So this should not be treated as a bucket-level statistic. + const NodeMaintenanceStats& total_replica_stats() const noexcept { + return _total_stats; } bool operator==(const NodeMaintenanceStatsTracker& rhs) const { - return _stats == rhs._stats; + return _node_stats == rhs._node_stats; } void merge(const NodeMaintenanceStatsTracker& rhs); }; diff --git a/vespajlib/src/main/java/com/yahoo/slime/BufferedOutput.java b/vespajlib/src/main/java/com/yahoo/slime/BufferedOutput.java index 0fabea77df0..4c76abd4707 100644 --- a/vespajlib/src/main/java/com/yahoo/slime/BufferedOutput.java +++ b/vespajlib/src/main/java/com/yahoo/slime/BufferedOutput.java @@ -3,6 +3,8 @@ package com.yahoo.slime; import com.yahoo.compress.Compressor; +import java.nio.charset.Charset; + final class BufferedOutput { private byte[] buf; @@ -56,6 +58,9 @@ final class BufferedOutput { System.arraycopy(buf, 0, ret, 0, pos); return ret; } + public String toString(Charset charset) { + return new String(buf, 0, pos, charset); + } Compressor.Compression compress(Compressor compressor) { return compressor.compress(buf, pos); } diff --git a/vespajlib/src/main/java/com/yahoo/slime/JsonDecoder.java b/vespajlib/src/main/java/com/yahoo/slime/JsonDecoder.java index b7f11f53cd5..682eccdab42 100644 --- a/vespajlib/src/main/java/com/yahoo/slime/JsonDecoder.java +++ b/vespajlib/src/main/java/com/yahoo/slime/JsonDecoder.java @@ -2,10 +2,9 @@ package com.yahoo.slime; import com.yahoo.text.Text; -import com.yahoo.text.Utf8; -import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; /** * A port of the C++ json decoder intended to be fast. @@ -20,7 +19,7 @@ public class JsonDecoder { private final SlimeInserter slimeInserter = new SlimeInserter(null); private final ArrayInserter arrayInserter = new ArrayInserter(null); private final ObjectInserter objectInserter = new ObjectInserter(null, null); - private final ByteArrayOutputStream buf = new ByteArrayOutputStream(); + private final BufferedOutput buf = new BufferedOutput(); private static final byte[] TRUE = {'t', 'r', 'u', 'e'}; private static final byte[] FALSE = {'f', 'a', 'l', 's', 'e'}; @@ -86,15 +85,15 @@ public class JsonDecoder { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+': case '-': - buf.write(c); + buf.put(c); next(); break; default: if (likelyFloatingPoint) { - double num = Double.parseDouble(Utf8.toString(buf.toByteArray())); + double num = Double.parseDouble(buf.toString(StandardCharsets.UTF_8)); inserter.insertDOUBLE(num); } else { - long num = Long.parseLong(Utf8.toString(buf.toByteArray())); + long num = Long.parseLong(buf.toString(StandardCharsets.UTF_8)); inserter.insertLONG(num); } return; @@ -103,8 +102,8 @@ public class JsonDecoder { } private void expect(byte[] expected) { - for (int i = 0; i < expected.length; i++) { - if ( ! skip(expected[i])) { + for (byte b : expected) { + if ( ! skip(b)) { in.fail("Unexpected " + characterToReadableString(c)); return; } @@ -150,9 +149,9 @@ public class JsonDecoder { default: for (;;) { switch (c) { - case ':': case ' ': case '\t': case '\n': case '\r': case '\0': return Utf8.toString(buf.toByteArray()); + case ':': case ' ': case '\t': case '\n': case '\r': case '\0': return buf.toString(StandardCharsets.UTF_8); default: - buf.write(c); + buf.put(c); next(); break; } @@ -176,13 +175,13 @@ public class JsonDecoder { next(); switch (c) { case '"': case '\\': case '/': case '\'': - buf.write(c); + buf.put(c); break; - case 'b': buf.write((byte) '\b'); break; - case 'f': buf.write((byte) '\f'); break; - case 'n': buf.write((byte) '\n'); break; - case 'r': buf.write((byte) '\r'); break; - case 't': buf.write((byte) '\t'); break; + case 'b': buf.put((byte) '\b'); break; + case 'f': buf.put((byte) '\f'); break; + case 'n': buf.put((byte) '\n'); break; + case 'r': buf.put((byte) '\r'); break; + case 't': buf.put((byte) '\t'); break; case 'u': writeUtf8(dequoteUtf16(), buf, 0xffffff80); continue; default: in.fail("Invalid quoted char(" + c + ")"); @@ -193,34 +192,34 @@ public class JsonDecoder { case '"': case '\'': if (c == quote) { next(); - return Utf8.toString(buf.toByteArray()); + return buf.toString(StandardCharsets.UTF_8); } else { - buf.write(c); + buf.put(c); next(); } break; case '\0': in.fail("Unterminated string"); - return Utf8.toString(buf.toByteArray()); + return buf.toString(StandardCharsets.UTF_8); default: - buf.write(c); + buf.put(c); next(); break; } } } - private static void writeUtf8(long codepoint, ByteArrayOutputStream buf, long mask) { + private static void writeUtf8(long codepoint, BufferedOutput buf, long mask) { if ((codepoint & mask) == 0) { - buf.write((byte) ((mask << 1) | codepoint)); + buf.put((byte) ((mask << 1) | codepoint)); } else { writeUtf8(codepoint >> 6, buf, mask >> (2 - ((mask >> 6) & 0x1))); - buf.write((byte) (0x80 | (codepoint & 0x3f))); + buf.put((byte) (0x80 | (codepoint & 0x3f))); } } - private static byte[] unicodeStart = {'\\', 'u'}; + private final static byte[] unicodeStart = {'\\', 'u'}; private long dequoteUtf16() { next(); long codepoint = readHexValue(4); |