Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-doc-endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenj authored Jan 15, 2025
2 parents ce855ec + e169a08 commit e2fac37
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 79 deletions.
1 change: 1 addition & 0 deletions catalyst-gateway/bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod cli;
mod db;
mod jinja;
mod logger;
mod metrics;
mod service;
mod settings;
mod utils;
Expand Down
1 change: 1 addition & 0 deletions catalyst-gateway/bin/src/metrics/chain_follower.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//! Metrics related to Chain Follower analytics.
1 change: 1 addition & 0 deletions catalyst-gateway/bin/src/metrics/chain_indexer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//! Metrics related to Chain Indexer analytics.
60 changes: 60 additions & 0 deletions catalyst-gateway/bin/src/metrics/endpoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Metrics related to endpoint analytics.
use std::sync::LazyLock;

use prometheus::{register_histogram_vec, register_int_counter_vec, HistogramVec, IntCounterVec};

/// Labels for the metrics
const METRIC_LABELS: [&str; 3] = ["endpoint", "method", "status_code"];
/// Labels for the client metrics
const CLIENT_METRIC_LABELS: [&str; 2] = ["client", "status_code"];

// Prometheus Metrics maintained by the service

/// HTTP Request duration histogram.
pub(crate) static HTTP_REQ_DURATION_MS: LazyLock<HistogramVec> = LazyLock::new(|| {
register_histogram_vec!(
"http_request_duration_ms",
"Duration of HTTP requests in milliseconds",
&METRIC_LABELS
)
.unwrap()
});

/// HTTP Request CPU Time histogram.
pub(crate) static HTTP_REQ_CPU_TIME_MS: LazyLock<HistogramVec> = LazyLock::new(|| {
register_histogram_vec!(
"http_request_cpu_time_ms",
"CPU Time of HTTP requests in milliseconds",
&METRIC_LABELS
)
.unwrap()
});

// No Tacho implemented to enable this.
// static ref HTTP_REQUEST_RATE: GaugeVec = register_gauge_vec!(
// "http_request_rate",
// "Rate of HTTP requests per second",
// &METRIC_LABELS
// )
// .unwrap();

/// HTTP Request count histogram.
pub(crate) static HTTP_REQUEST_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
register_int_counter_vec!(
"http_request_count",
"Number of HTTP requests",
&METRIC_LABELS
)
.unwrap()
});

/// Client Request Count histogram.
pub(crate) static CLIENT_REQUEST_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
register_int_counter_vec!(
"client_request_count",
"Number of HTTP requests per client",
&CLIENT_METRIC_LABELS
)
.unwrap()
});
1 change: 1 addition & 0 deletions catalyst-gateway/bin/src/metrics/memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//! Metrics related to memory analytics.
18 changes: 18 additions & 0 deletions catalyst-gateway/bin/src/metrics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! This module contains submodules related to metrics report and analytics.
use prometheus::{default_registry, Registry};

pub(crate) mod chain_follower;
pub(crate) mod chain_indexer;
pub(crate) mod endpoint;
pub(crate) mod memory;

/// Initialize Prometheus metrics.
///
/// ## Returns
///
/// Returns the default prometheus registry.
#[must_use]
pub(crate) fn init_prometheus() -> Registry {
default_registry().clone()
}
3 changes: 2 additions & 1 deletion catalyst-gateway/bin/src/service/poem_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ use poem::{
};

use crate::{
metrics::init_prometheus,
service::{
api::mk_api,
docs::{docs, favicon},
utilities::{
catch_panic::{set_panic_hook, ServicePanicHandler},
middleware::tracing_mw::{init_prometheus, Tracing},
middleware::tracing_mw::Tracing,
},
},
settings::Settings,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Full Tracing and metrics middleware.
use std::{sync::LazyLock, time::Instant};
use std::time::Instant;

use cpu_time::ProcessTime; // ThreadTime doesn't work.
use poem::{
Expand All @@ -8,74 +9,17 @@ use poem::{
Endpoint, Error, FromRequest, IntoResponse, Middleware, PathPattern, Request, Response, Result,
};
use poem_openapi::OperationId;
use prometheus::{
default_registry, register_histogram_vec, register_int_counter_vec, HistogramVec,
IntCounterVec, Registry,
};
use tracing::{error, field, Instrument, Level, Span};
use ulid::Ulid;
use uuid::Uuid;

use crate::{settings::Settings, utils::blake2b_hash::generate_uuid_string_from_data};

/// Labels for the metrics
const METRIC_LABELS: [&str; 3] = ["endpoint", "method", "status_code"];
/// Labels for the client metrics
const CLIENT_METRIC_LABELS: [&str; 2] = ["client", "status_code"];

// Prometheus Metrics maintained by the service

/// HTTP Request duration histogram.
static HTTP_REQ_DURATION_MS: LazyLock<HistogramVec> = LazyLock::new(|| {
#[allow(clippy::ignored_unit_patterns)]
register_histogram_vec!(
"http_request_duration_ms",
"Duration of HTTP requests in milliseconds",
&METRIC_LABELS
)
.unwrap()
});

/// HTTP Request CPU Time histogram.
static HTTP_REQ_CPU_TIME_MS: LazyLock<HistogramVec> = LazyLock::new(|| {
#[allow(clippy::ignored_unit_patterns)]
register_histogram_vec!(
"http_request_cpu_time_ms",
"CPU Time of HTTP requests in milliseconds",
&METRIC_LABELS
)
.unwrap()
});

// No Tacho implemented to enable this.
// static ref HTTP_REQUEST_RATE: GaugeVec = register_gauge_vec!(
// "http_request_rate",
// "Rate of HTTP requests per second",
// &METRIC_LABELS
// )
// .unwrap();

/// HTTP Request count histogram.
static HTTP_REQUEST_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
#[allow(clippy::ignored_unit_patterns)]
register_int_counter_vec!(
"http_request_count",
"Number of HTTP requests",
&METRIC_LABELS
)
.unwrap()
});

/// Client Request Count histogram.
static CLIENT_REQUEST_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
#[allow(clippy::ignored_unit_patterns)]
register_int_counter_vec!(
"client_request_count",
"Number of HTTP requests per client",
&CLIENT_METRIC_LABELS
)
.unwrap()
});
use crate::{
metrics::endpoint::{
CLIENT_REQUEST_COUNT, HTTP_REQUEST_COUNT, HTTP_REQ_CPU_TIME_MS, HTTP_REQ_DURATION_MS,
},
settings::Settings,
utils::blake2b_hash::generate_uuid_string_from_data,
};

// Currently no way to get these values. TODO.
// Panic Request Count histogram.
Expand Down Expand Up @@ -389,13 +333,3 @@ impl<E: Endpoint> Endpoint for TracingEndpoint<E> {
response
}
}

/// Initialize Prometheus metrics.
///
/// ## Returns
///
/// Returns the default prometheus registry.
#[must_use]
pub(crate) fn init_prometheus() -> Registry {
default_registry().clone()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import 'package:catalyst_voices/common/ext/document_property_ext.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
import 'package:flutter/material.dart';

class YesNoChoiceWidget extends StatefulWidget {
final DocumentProperty<bool> property;
final ValueChanged<DocumentChange> onChanged;
final bool isEditMode;
final bool isRequired;

const YesNoChoiceWidget({
super.key,
required this.property,
required this.onChanged,
required this.isEditMode,
required this.isRequired,
});

@override
State<YesNoChoiceWidget> createState() => _YesNoChoiceWidgetState();
}

class _YesNoChoiceWidgetState extends State<YesNoChoiceWidget> {
late bool? selectedValue;

String get _description => widget.property.formattedDescription;

@override
void initState() {
super.initState();

_handleInitialValue();
}

@override
void didUpdateWidget(covariant YesNoChoiceWidget oldWidget) {
super.didUpdateWidget(oldWidget);

if (oldWidget.property.value != widget.property.value) {
_handleInitialValue();
}

if (oldWidget.isEditMode != widget.isEditMode &&
widget.isEditMode == false) {
_handleInitialValue();
}
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (_description.isNotEmpty) ...[
Text(
_description,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
],
_YesNoChoiceSegmentButton(
context,
value: selectedValue,
enabled: widget.isEditMode,
onChanged: _handleValueChanged,
validator: (value) {
// TODO(dtscalac): add validation
final result = widget.property.schema.validatePropertyValue(value);

return LocalizedDocumentValidationResult.from(result)
.message(context);
},
),
],
);
}

void _handleInitialValue() {
selectedValue = widget.property.value;
}

void _handleValueChanged(bool? value) {
setState(() {
selectedValue = value;
});
if (value == null && widget.property.value != value) {
_notifyChangeListener(value);
}
}

void _notifyChangeListener(bool? value) {
widget.onChanged(
DocumentChange(
nodeId: widget.property.schema.nodeId,
value: value,
),
);
}
}

class _YesNoChoiceSegmentButton extends FormField<bool?> {
final bool? value;
final ValueChanged<bool?>? onChanged;

_YesNoChoiceSegmentButton(
BuildContext context, {
super.key,
required this.value,
required this.onChanged,
super.validator,
super.enabled,
AutovalidateMode autovalidateMode = AutovalidateMode.onUserInteraction,
}) : super(
initialValue: value,
autovalidateMode: autovalidateMode,
builder: (field) {
void onChangedHandler(Set<bool> selected) {
final newValue = selected.isEmpty ? null : selected.first;
field.didChange(newValue);
onChanged?.call(newValue);
}

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
IgnorePointer(
ignoring: !enabled,
child: VoicesSegmentedButton<bool>(
key: key,
segments: [
ButtonSegment(
value: true,
label: Text(context.l10n.yes),
),
ButtonSegment(
value: false,
label: Text(context.l10n.no),
),
],
selected: value != null ? {value} : {},
onChanged: onChangedHandler,
emptySelectionAllowed: true,
style: _getButtonStyle(field),
),
),
if (field.hasError)
Text(
field.errorText ?? context.l10n.snackbarErrorLabelText,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: Theme.of(context).colorScheme.error),
),
],
);
},
);

static ButtonStyle? _getButtonStyle(FormFieldState<bool?> field) {
if (field.errorText == null) return null;

return ButtonStyle(
side: WidgetStatePropertyAll(
BorderSide(
color: Theme.of(field.context).colorScheme.error,
),
),
);
}
}
Loading

0 comments on commit e2fac37

Please sign in to comment.