diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock
index 0e99ef84e556f0e82f0282af91e2aa4ab6e272ed..0195d5673b084e21f4f62e6540a3c1d3a3e9e3fb 100644
--- a/polkadot/Cargo.lock
+++ b/polkadot/Cargo.lock
@@ -5407,6 +5407,7 @@ dependencies = [
  "polkadot-node-primitives",
  "polkadot-node-subsystem",
  "polkadot-node-subsystem-test-helpers",
+ "polkadot-node-subsystem-util",
  "polkadot-overseer",
  "polkadot-primitives",
  "rand_core 0.5.1",
diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml
index 84577e41df63442fa69b9e9aa9bf0a1535c63a41..c0779e786f3bceea1bc826aec60e460398e4a864 100644
--- a/polkadot/node/core/approval-voting/Cargo.toml
+++ b/polkadot/node/core/approval-voting/Cargo.toml
@@ -17,7 +17,8 @@ kvdb = "0.9.0"
 kvdb-rocksdb = "0.11.0"
 derive_more = "0.99.1"
 
-polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" }
+polkadot-node-subsystem = { path = "../../subsystem" }
+polkadot-node-subsystem-util = { path = "../../subsystem-util" }
 polkadot-overseer = { path = "../../overseer" }
 polkadot-primitives = { path = "../../../primitives" }
 polkadot-node-primitives = { path = "../../primitives" }
diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs
index 1ad981f74a4452f04ebae6732c3680d294df9ca9..f902fbb058639c73c106a038e130174925541998 100644
--- a/polkadot/node/core/approval-voting/src/import.rs
+++ b/polkadot/node/core/approval-voting/src/import.rs
@@ -28,7 +28,7 @@
 //!
 //! We maintain a rolling window of session indices. This starts as empty
 
-use polkadot_subsystem::{
+use polkadot_node_subsystem::{
 	messages::{
 		RuntimeApiMessage, RuntimeApiRequest, ChainApiMessage, ApprovalDistributionMessage,
 	},
@@ -708,7 +708,7 @@ mod tests {
 	use polkadot_node_subsystem_test_helpers::make_subsystem_context;
 	use polkadot_node_primitives::approval::{VRFOutput, VRFProof};
 	use polkadot_primitives::v1::ValidatorIndex;
-	use polkadot_subsystem::messages::AllMessages;
+	use polkadot_node_subsystem::messages::AllMessages;
 	use sp_core::testing::TaskExecutor;
 	use sp_runtime::{Digest, DigestItem};
 	use sp_consensus_babe::Epoch as BabeEpoch;
diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs
index 60f1135f70e0fa08370c659e50b6d0d18cf0f4f0..7fcfd475c1b7736242b84e4958877bde69f09c92 100644
--- a/polkadot/node/core/approval-voting/src/lib.rs
+++ b/polkadot/node/core/approval-voting/src/lib.rs
@@ -21,7 +21,7 @@
 //! of others. It uses this information to determine when candidates and blocks have
 //! been sufficiently approved to finalize.
 
-use polkadot_subsystem::{
+use polkadot_node_subsystem::{
 	messages::{
 		AssignmentCheckResult, ApprovalCheckResult, ApprovalVotingMessage,
 		RuntimeApiMessage, RuntimeApiRequest, ChainApiMessage, ApprovalDistributionMessage,
@@ -31,6 +31,9 @@ use polkadot_subsystem::{
 	Subsystem, SubsystemContext, SubsystemError, SubsystemResult, SpawnedSubsystem,
 	FromOverseer, OverseerSignal,
 };
+use polkadot_node_subsystem_util::{
+	metrics::{self, prometheus},
+};
 use polkadot_primitives::v1::{
 	ValidatorIndex, Hash, SessionIndex, SessionInfo, CandidateHash,
 	CandidateReceipt, BlockNumber, PersistedValidationData,
@@ -94,15 +97,79 @@ pub struct ApprovalVotingSubsystem {
 	keystore: Arc<LocalKeystore>,
 	slot_duration_millis: u64,
 	db: Arc<dyn KeyValueDB>,
+	metrics: Metrics,
+}
+
+#[derive(Clone)]
+struct MetricsInner {
+	imported_candidates_total: prometheus::Counter<prometheus::U64>,
+	assignments_produced_total: prometheus::Counter<prometheus::U64>,
+	approvals_produced_total: prometheus::Counter<prometheus::U64>,
+}
+
+/// Aproval Voting metrics.
+#[derive(Default, Clone)]
+pub struct Metrics(Option<MetricsInner>);
+
+impl Metrics {
+	fn on_candidate_imported(&self) {
+		if let Some(metrics) = &self.0 {
+			metrics.imported_candidates_total.inc();
+		}
+	}
+
+	fn on_assignment_produced(&self) {
+		if let Some(metrics) = &self.0 {
+			metrics.assignments_produced_total.inc();
+		}
+	}
+
+	fn on_approval_produced(&self) {
+		if let Some(metrics) = &self.0 {
+			metrics.approvals_produced_total.inc();
+		}
+	}
+}
+
+impl metrics::Metrics for Metrics {
+	fn try_register(
+		registry: &prometheus::Registry,
+	) -> std::result::Result<Self, prometheus::PrometheusError> {
+		let metrics = MetricsInner {
+			imported_candidates_total: prometheus::register(
+				prometheus::Counter::new(
+					"parachain_imported_candidates_total",
+					"Number of candidates imported by the approval voting subsystem",
+				)?,
+				registry,
+			)?,
+			assignments_produced_total: prometheus::register(
+				prometheus::Counter::new(
+					"parachain_assignments_produced_total",
+					"Number of assignments produced by the approval voting subsystem",
+				)?,
+				registry,
+			)?,
+			approvals_produced_total: prometheus::register(
+				prometheus::Counter::new(
+					"parachain_approvals_produced_total",
+					"Number of approvals produced by the approval voting subsystem",
+				)?,
+				registry,
+			)?,
+		};
+		Ok(Metrics(Some(metrics)))
+	}
 }
 
 impl ApprovalVotingSubsystem {
-	/// Create a new approval voting subsystem with the given keystore, slot duration,
+	/// Create a new approval voting subsystem with the given keystore and config,
 	/// which creates a DB at the given path. This function will delete the directory
 	/// at the given path if it already exists.
 	pub fn with_config(
 		config: Config,
 		keystore: Arc<LocalKeystore>,
+		metrics: Metrics,
 	) -> std::io::Result<Self> {
 		const DEFAULT_CACHE_SIZE: usize = 100 * 1024 * 1024; // 100MiB default should be fine unless finality stalls.
 
@@ -115,6 +182,7 @@ impl ApprovalVotingSubsystem {
 			keystore,
 			slot_duration_millis: config.slot_duration_millis,
 			db,
+			metrics,
 		})
 	}
 }
@@ -343,6 +411,7 @@ async fn run<C>(
 				handle_from_overseer(
 					&mut ctx,
 					&mut state,
+					&subsystem.metrics,
 					db_writer,
 					next_msg?,
 					&mut last_finalized_height,
@@ -353,6 +422,7 @@ async fn run<C>(
 					handle_background_request(
 						&mut ctx,
 						&mut state,
+						&subsystem.metrics,
 						req,
 					).await?
 				} else {
@@ -363,6 +433,7 @@ async fn run<C>(
 
 		if handle_actions(
 			&mut ctx,
+			&subsystem.metrics,
 			&mut wakeups,
 			db_writer,
 			&background_tx,
@@ -378,6 +449,7 @@ async fn run<C>(
 // returns `true` if any of the actions was a `Conclude` command.
 async fn handle_actions(
 	ctx: &mut impl SubsystemContext,
+	metrics: &Metrics,
 	wakeups: &mut Wakeups,
 	db: &dyn KeyValueDB,
 	background_tx: &mpsc::Sender<BackgroundRequest>,
@@ -406,6 +478,7 @@ async fn handle_actions(
 				candidate,
 				backing_group,
 			} => {
+				metrics.on_assignment_produced();
 				let block_hash = indirect_cert.block_hash;
 				let validator_index = indirect_cert.validator;
 
@@ -439,6 +512,7 @@ async fn handle_actions(
 async fn handle_from_overseer(
 	ctx: &mut impl SubsystemContext,
 	state: &mut State<impl DBReader>,
+	metrics: &Metrics,
 	db_writer: &dyn KeyValueDB,
 	x: FromOverseer<ApprovalVotingMessage>,
 	last_finalized_height: &mut Option<BlockNumber>,
@@ -468,6 +542,8 @@ async fn handle_from_overseer(
 							);
 
 							for (c_hash, c_entry) in block_batch.imported_candidates {
+								metrics.on_candidate_imported();
+
 								let our_tranche = c_entry
 									.approval_entry(&block_batch.block_hash)
 									.and_then(|a| a.our_assignment().map(|a| a.tranche()));
@@ -542,11 +618,12 @@ async fn handle_from_overseer(
 async fn handle_background_request(
 	ctx: &mut impl SubsystemContext,
 	state: &State<impl DBReader>,
+	metrics: &Metrics,
 	request: BackgroundRequest,
 ) -> SubsystemResult<Vec<Action>> {
 	match request {
 		BackgroundRequest::ApprovalVote(vote_request) => {
-			issue_approval(ctx, state, vote_request).await
+			issue_approval(ctx, state, metrics, vote_request).await
 		}
 		BackgroundRequest::CandidateValidation(
 			validation_data,
@@ -1443,6 +1520,7 @@ async fn launch_approval(
 async fn issue_approval(
 	ctx: &mut impl SubsystemContext,
 	state: &State<impl DBReader>,
+	metrics: &Metrics,
 	request: ApprovalVoteRequest,
 ) -> SubsystemResult<Vec<Action>> {
 	let ApprovalVoteRequest { validator_index, block_hash, candidate_index } = request;
@@ -1541,6 +1619,8 @@ async fn issue_approval(
 		validator_index as _,
 	)?;
 
+	metrics.on_approval_produced();
+
 	// dispatch to approval distribution.
 	ctx.send_message(ApprovalDistributionMessage::DistributeApproval(IndirectSignedApprovalVote {
 		block_hash,
diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs
index 160ed7aadf79288e5091fc48f0941b4bcdcf1aa3..3959a1468be67e4a526c593849044ac0777fdb17 100644
--- a/polkadot/node/core/approval-voting/src/tests.rs
+++ b/polkadot/node/core/approval-voting/src/tests.rs
@@ -21,7 +21,7 @@ use polkadot_node_primitives::approval::{
 	RELAY_VRF_MODULO_CONTEXT, DelayTranche,
 };
 use polkadot_node_subsystem_test_helpers::make_subsystem_context;
-use polkadot_subsystem::messages::AllMessages;
+use polkadot_node_subsystem::messages::AllMessages;
 use sp_core::testing::TaskExecutor;
 
 use parking_lot::Mutex;
diff --git a/polkadot/node/service/src/grandpa_support.rs b/polkadot/node/service/src/grandpa_support.rs
index 70df2be23ddde9697c439e1e1985a93a007c206b..991df8b6708fbbb423eed8472a1cb7f2b656d239 100644
--- a/polkadot/node/service/src/grandpa_support.rs
+++ b/polkadot/node/service/src/grandpa_support.rs
@@ -57,7 +57,7 @@ impl ApprovalCheckingDiagnostic {
 				Some(prometheus_endpoint::register(
 					prometheus_endpoint::Histogram::with_opts(
 						prometheus_endpoint::HistogramOpts::new(
-							"approval_checking_finality_lag",
+							"parachain_approval_checking_finality_lag",
 							"How far behind the head of the chain the Approval Checking protocol wants to vote",
 						).buckets(vec![1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 20.0, 30.0, 40.0, 50.0])
 					)?,
diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs
index e1e816797968c2aef5d9cd26181f91fd4a078b01..ee3de73918a59b82745bc141dc0f8d8f9ecdf3a3 100644
--- a/polkadot/node/service/src/lib.rs
+++ b/polkadot/node/service/src/lib.rs
@@ -497,6 +497,7 @@ where
 		approval_voting: ApprovalVotingSubsystem::with_config(
 			approval_voting_config,
 			keystore.clone(),
+			Metrics::register(registry)?,
 		)?,
 		gossip_support: GossipSupportSubsystem::new(),
 	};