From aa26ab830205690ea4fdcc6633a6eb9ff7a4241b Mon Sep 17 00:00:00 2001 From: thenav56 Date: Fri, 17 Oct 2025 20:14:26 +0545 Subject: [PATCH 1/2] feat(monitoring): setup alloy --- monitoring/.env.sample | 16 +++ monitoring/config.alloy | 171 +++++++++++++++++++++++++++++++++ monitoring/docker-compose.yaml | 26 +++++ 3 files changed, 213 insertions(+) create mode 100644 monitoring/.env.sample create mode 100644 monitoring/config.alloy create mode 100644 monitoring/docker-compose.yaml diff --git a/monitoring/.env.sample b/monitoring/.env.sample new file mode 100644 index 0000000..1656a3d --- /dev/null +++ b/monitoring/.env.sample @@ -0,0 +1,16 @@ +GCLOUD_RW_API_KEY="XYZ" + +MAPSWIPE_ENV=mapswipe-test +# Fleep management +GCLOUD_FM_ID=mapswipe-test +GCLOUD_FM_URL="https://fleet-management-xyz.grafana.net" +GCLOUD_FM_HOSTED_ID="NUMBER" +GCLOUD_FM_POLL_FREQUENCY="60s" + +# Loki +GCLOUD_HOSTED_LOGS_URL="https://logs-xyz.grafana.net/loki/api/v1/push" +GCLOUD_HOSTED_LOGS_ID="NUMBER" + +# Prometheus +GCLOUD_HOSTED_METRICS_URL="https://prometheus-xyz.grafana.net/api/prom/push" +GCLOUD_HOSTED_METRICS_ID="NUMBER" diff --git a/monitoring/config.alloy b/monitoring/config.alloy new file mode 100644 index 0000000..1616f35 --- /dev/null +++ b/monitoring/config.alloy @@ -0,0 +1,171 @@ +logging { + level = "info" + format = "logfmt" +} + +locals { + scrape_interval = "20s" + + gc_prom_url = sys.env("GCLOUD_HOSTED_METRICS_URL") + gc_prom_id = sys.env("GCLOUD_HOSTED_METRICS_ID") + + gc_loki_url = sys.env("GCLOUD_HOSTED_LOGS_URL") + gc_loki_id = sys.env("GCLOUD_HOSTED_LOGS_ID") + + gc_api_key = sys.env("GCLOUD_RW_API_KEY") + + hostname = env("HOSTNAME") + + host_root = "/host/root" +} + +remotecfg { + id = sys.env("GCLOUD_FM_ID") + url = sys.env("GCLOUD_FM_URL") + poll_frequency = sys.env("GCLOUD_FM_POLL_FREQUENCY") + + basic_auth { + username = sys.env("GCLOUD_FM_HOSTED_ID") + password = local.gc_api_key + } +} + +// ############################### +// #### Metrics Configuration #### +// ############################### + +// Configure a prometheus.remote_write component to send metrics to a Prometheus server. +prometheus.remote_write "metrics_service" { + endpoint { + url = local.gc_prom_url + + basic_auth { + username = local.gc_prom_id + password = local.gc_api_key + } + } +} + +// HOST ------------ // + +discovery.relabel "integrations_node_exporter" { + targets = prometheus.exporter.unix.integrations_node_exporter.targets + + rule { + target_label = "instance" + replacement = local.hostname + } + + rule { + target_label = "job" + replacement = "integrations/node_exporter" + } +} + +prometheus.exporter.unix "integrations_node_exporter" { + disable_collectors = ["ipvs", "btrfs", "infiniband", "xfs", "zfs"] + + filesystem { + fs_types_exclude = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|tmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$" + mount_points_exclude = "^/(dev|proc|run/credentials/.+|sys|var/lib/docker/.+)($|/)" + mount_timeout = "5s" + } + + netclass { + ignored_devices = "^(veth.*|cali.*|[a-f0-9]{15})$" + } + + netdev { + device_exclude = "^(veth.*|cali.*|[a-f0-9]{15})$" + } +} + +prometheus.scrape "integrations_node_exporter" { + targets = discovery.relabel.integrations_node_exporter.output + forward_to = [prometheus.relabel.integrations_node_exporter.receiver] +} + +prometheus.relabel "integrations_node_exporter" { + forward_to = [prometheus.remote_write.metrics_service.receiver] + + rule { + source_labels = ["__name__"] + regex = "up|node_arp_entries|node_boot_time_seconds|node_context_switches_total|node_cpu_seconds_total|node_disk_io_time_seconds_total|node_disk_io_time_weighted_seconds_total|node_disk_read_bytes_total|node_disk_read_time_seconds_total|node_disk_reads_completed_total|node_disk_write_time_seconds_total|node_disk_writes_completed_total|node_disk_written_bytes_total|node_filefd_allocated|node_filefd_maximum|node_filesystem_avail_bytes|node_filesystem_device_error|node_filesystem_files|node_filesystem_files_free|node_filesystem_readonly|node_filesystem_size_bytes|node_intr_total|node_load1|node_load15|node_load5|node_md_disks|node_md_disks_required|node_memory_Active_anon_bytes|node_memory_Active_bytes|node_memory_Active_file_bytes|node_memory_AnonHugePages_bytes|node_memory_AnonPages_bytes|node_memory_Bounce_bytes|node_memory_Buffers_bytes|node_memory_Cached_bytes|node_memory_CommitLimit_bytes|node_memory_Committed_AS_bytes|node_memory_DirectMap1G_bytes|node_memory_DirectMap2M_bytes|node_memory_DirectMap4k_bytes|node_memory_Dirty_bytes|node_memory_HugePages_Free|node_memory_HugePages_Rsvd|node_memory_HugePages_Surp|node_memory_HugePages_Total|node_memory_Hugepagesize_bytes|node_memory_Inactive_anon_bytes|node_memory_Inactive_bytes|node_memory_Inactive_file_bytes|node_memory_Mapped_bytes|node_memory_MemAvailable_bytes|node_memory_MemFree_bytes|node_memory_MemTotal_bytes|node_memory_SReclaimable_bytes|node_memory_SUnreclaim_bytes|node_memory_ShmemHugePages_bytes|node_memory_ShmemPmdMapped_bytes|node_memory_Shmem_bytes|node_memory_Slab_bytes|node_memory_SwapTotal_bytes|node_memory_VmallocChunk_bytes|node_memory_VmallocTotal_bytes|node_memory_VmallocUsed_bytes|node_memory_WritebackTmp_bytes|node_memory_Writeback_bytes|node_netstat_Icmp6_InErrors|node_netstat_Icmp6_InMsgs|node_netstat_Icmp6_OutMsgs|node_netstat_Icmp_InErrors|node_netstat_Icmp_InMsgs|node_netstat_Icmp_OutMsgs|node_netstat_IpExt_InOctets|node_netstat_IpExt_OutOctets|node_netstat_TcpExt_ListenDrops|node_netstat_TcpExt_ListenOverflows|node_netstat_TcpExt_TCPSynRetrans|node_netstat_Tcp_InErrs|node_netstat_Tcp_InSegs|node_netstat_Tcp_OutRsts|node_netstat_Tcp_OutSegs|node_netstat_Tcp_RetransSegs|node_netstat_Udp6_InDatagrams|node_netstat_Udp6_InErrors|node_netstat_Udp6_NoPorts|node_netstat_Udp6_OutDatagrams|node_netstat_Udp6_RcvbufErrors|node_netstat_Udp6_SndbufErrors|node_netstat_UdpLite_InErrors|node_netstat_Udp_InDatagrams|node_netstat_Udp_InErrors|node_netstat_Udp_NoPorts|node_netstat_Udp_OutDatagrams|node_netstat_Udp_RcvbufErrors|node_netstat_Udp_SndbufErrors|node_network_carrier|node_network_info|node_network_mtu_bytes|node_network_receive_bytes_total|node_network_receive_compressed_total|node_network_receive_drop_total|node_network_receive_errs_total|node_network_receive_fifo_total|node_network_receive_multicast_total|node_network_receive_packets_total|node_network_speed_bytes|node_network_transmit_bytes_total|node_network_transmit_compressed_total|node_network_transmit_drop_total|node_network_transmit_errs_total|node_network_transmit_fifo_total|node_network_transmit_multicast_total|node_network_transmit_packets_total|node_network_transmit_queue_length|node_network_up|node_nf_conntrack_entries|node_nf_conntrack_entries_limit|node_os_info|node_sockstat_FRAG6_inuse|node_sockstat_FRAG_inuse|node_sockstat_RAW6_inuse|node_sockstat_RAW_inuse|node_sockstat_TCP6_inuse|node_sockstat_TCP_alloc|node_sockstat_TCP_inuse|node_sockstat_TCP_mem|node_sockstat_TCP_mem_bytes|node_sockstat_TCP_orphan|node_sockstat_TCP_tw|node_sockstat_UDP6_inuse|node_sockstat_UDPLITE6_inuse|node_sockstat_UDPLITE_inuse|node_sockstat_UDP_inuse|node_sockstat_UDP_mem|node_sockstat_UDP_mem_bytes|node_sockstat_sockets_used|node_softnet_dropped_total|node_softnet_processed_total|node_softnet_times_squeezed_total|node_systemd_unit_state|node_textfile_scrape_error|node_time_zone_offset_seconds|node_timex_estimated_error_seconds|node_timex_maxerror_seconds|node_timex_offset_seconds|node_timex_sync_status|node_uname_info|node_vmstat_oom_kill|node_vmstat_pgfault|node_vmstat_pgmajfault|node_vmstat_pgpgin|node_vmstat_pgpgout|node_vmstat_pswpin|node_vmstat_pswpout|process_max_fds|process_open_fds" + action = "keep" + } +} + +// DOCKER ------------ // + +// Host Cadvisor on the Docker socket to expose container metrics. +prometheus.exporter.cadvisor "linux" { + docker_only = true +} + +discovery.relabel "linux" { + targets = prometheus.exporter.cadvisor.linux.targets + + rule { + target_label = "job" + replacement = "integrations/docker" + } + + rule { + target_label = "instance" + replacement = local.hostname + } +} + +// Configure a prometheus.scrape component to collect cadvisor metrics. +prometheus.scrape "scraper" { + targets = discovery.relabel.linux.output + forward_to = [prometheus.remote_write.metrics_service.receiver] + + scrape_interval = local.scrape_interval +} + +// ############################### +// #### Logging Configuration #### +// ############################### + +loki.write "grafana_cloud_loki" { + endpoint { + url = local.gc_loki_url + + basic_auth { + username = local.gc_loki_id + password = local.gc_api_key + } + } +} + +// DOCKER ------------ // + +// Discover Docker containers and extract metadata. +discovery.docker "linux" { + host = "unix:///var/run/docker.sock" +} + +// Define a relabeling rule to create a service name from the container name. +discovery.relabel "logs_integrations_docker" { + targets = [] + + rule { + source_labels = ["__meta_docker_container_name"] + regex = "/(.*)" + target_label = "container_name" + } + + rule { + target_label = "instance" + replacement = local.hostname + } +} + +// Configure a loki.source.docker component to collect logs from Docker containers. +loki.source.docker "default" { + host = "unix:///var/run/docker.sock" + targets = discovery.docker.linux.targets + relabel_rules = discovery.relabel.logs_integrations_docker.rules + forward_to = [loki.write.grafana_cloud_loki.receiver] +} diff --git a/monitoring/docker-compose.yaml b/monitoring/docker-compose.yaml new file mode 100644 index 0000000..ffc9dcd --- /dev/null +++ b/monitoring/docker-compose.yaml @@ -0,0 +1,26 @@ +services: + alloy: + image: grafana/alloy:latest + container_name: grafana_alloy + restart: unless-stopped + ports: + - '127.0.0.1:12345:12345' + command: + - run + - --server.http.listen-addr=0.0.0.0:12345 + - --storage.path=/var/lib/alloy/data + - /etc/alloy/config.alloy + env_file: + - .env + volumes: + # mount config file + - './config.alloy:/etc/alloy/config.alloy' + # give access to running docker containers for discovery.docker + - /var/run/docker.sock:/var/run/docker.sock:ro + # give access to docker's log files directory (optional) + - /var/lib/docker/containers:/var/lib/docker/containers:ro + # Host + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/host/root:ro + - /var/log:/host/var/log:ro From b4e03034a81757a32ad86088cfa2e7b5c9b451b2 Mon Sep 17 00:00:00 2001 From: thenav56 Date: Fri, 17 Oct 2025 20:47:16 +0545 Subject: [PATCH 2/2] WIP --- monitoring/config.alloy | 215 ++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 131 deletions(-) diff --git a/monitoring/config.alloy b/monitoring/config.alloy index 1616f35..e21ea75 100644 --- a/monitoring/config.alloy +++ b/monitoring/config.alloy @@ -4,7 +4,7 @@ logging { } locals { - scrape_interval = "20s" + scrape_interval = "30s" gc_prom_url = sys.env("GCLOUD_HOSTED_METRICS_URL") gc_prom_id = sys.env("GCLOUD_HOSTED_METRICS_ID") @@ -19,153 +19,106 @@ locals { host_root = "/host/root" } -remotecfg { - id = sys.env("GCLOUD_FM_ID") - url = sys.env("GCLOUD_FM_URL") - poll_frequency = sys.env("GCLOUD_FM_POLL_FREQUENCY") - - basic_auth { - username = sys.env("GCLOUD_FM_HOSTED_ID") - password = local.gc_api_key - } -} - -// ############################### -// #### Metrics Configuration #### -// ############################### - -// Configure a prometheus.remote_write component to send metrics to a Prometheus server. -prometheus.remote_write "metrics_service" { - endpoint { - url = local.gc_prom_url - - basic_auth { - username = local.gc_prom_id - password = local.gc_api_key - } - } -} - -// HOST ------------ // - -discovery.relabel "integrations_node_exporter" { - targets = prometheus.exporter.unix.integrations_node_exporter.targets - - rule { - target_label = "instance" - replacement = local.hostname - } - - rule { - target_label = "job" - replacement = "integrations/node_exporter" - } +// locals { +// scrape_interval = "15s" +// gc_prom_url = "https://prometheus-prod-XX.grafana.net/api/prom/push" +// gc_loki_url = "https://logs-prod-XX.grafana.net/loki/api/v1/push" +// gc_username = "" +// gc_api_key = "" +// host_root = "/host/root" +// hostname = env("HOSTNAME") +// } + +// remotecfg { +// id = sys.env("GCLOUD_FM_ID") +// url = sys.env("GCLOUD_FM_URL") +// poll_frequency = sys.env("GCLOUD_FM_POLL_FREQUENCY") +// +// basic_auth { +// username = sys.env("GCLOUD_FM_HOSTED_ID") +// password = local.gc_api_key +// } +// } + +otelcol.processor.resource "add_instance_label" { + attributes { + key = "instance" + value = local.hostname + action = "insert" + } } -prometheus.exporter.unix "integrations_node_exporter" { - disable_collectors = ["ipvs", "btrfs", "infiniband", "xfs", "zfs"] - - filesystem { - fs_types_exclude = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|tmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$" - mount_points_exclude = "^/(dev|proc|run/credentials/.+|sys|var/lib/docker/.+)($|/)" - mount_timeout = "5s" - } +otelcol.receiver.hostmetrics "default" { + collection_interval = local.scrape_interval - netclass { - ignored_devices = "^(veth.*|cali.*|[a-f0-9]{15})$" + scrapers = { + cpu = {} + disk = { + mount_point = local.host_root } - - netdev { - device_exclude = "^(veth.*|cali.*|[a-f0-9]{15})$" + filesystem = { + mount_point = local.host_root } + load = {} + memory = {} + network = {} + paging = {} + uptime = {} + processes = {} + } + + root_path = local.host_root } -prometheus.scrape "integrations_node_exporter" { - targets = discovery.relabel.integrations_node_exporter.output - forward_to = [prometheus.relabel.integrations_node_exporter.receiver] +otelcol.receiver.docker_stats "default" { + endpoint = "unix:///var/run/docker.sock" + collection_interval = local.scrape_interval } -prometheus.relabel "integrations_node_exporter" { - forward_to = [prometheus.remote_write.metrics_service.receiver] - - rule { - source_labels = ["__name__"] - regex = "up|node_arp_entries|node_boot_time_seconds|node_context_switches_total|node_cpu_seconds_total|node_disk_io_time_seconds_total|node_disk_io_time_weighted_seconds_total|node_disk_read_bytes_total|node_disk_read_time_seconds_total|node_disk_reads_completed_total|node_disk_write_time_seconds_total|node_disk_writes_completed_total|node_disk_written_bytes_total|node_filefd_allocated|node_filefd_maximum|node_filesystem_avail_bytes|node_filesystem_device_error|node_filesystem_files|node_filesystem_files_free|node_filesystem_readonly|node_filesystem_size_bytes|node_intr_total|node_load1|node_load15|node_load5|node_md_disks|node_md_disks_required|node_memory_Active_anon_bytes|node_memory_Active_bytes|node_memory_Active_file_bytes|node_memory_AnonHugePages_bytes|node_memory_AnonPages_bytes|node_memory_Bounce_bytes|node_memory_Buffers_bytes|node_memory_Cached_bytes|node_memory_CommitLimit_bytes|node_memory_Committed_AS_bytes|node_memory_DirectMap1G_bytes|node_memory_DirectMap2M_bytes|node_memory_DirectMap4k_bytes|node_memory_Dirty_bytes|node_memory_HugePages_Free|node_memory_HugePages_Rsvd|node_memory_HugePages_Surp|node_memory_HugePages_Total|node_memory_Hugepagesize_bytes|node_memory_Inactive_anon_bytes|node_memory_Inactive_bytes|node_memory_Inactive_file_bytes|node_memory_Mapped_bytes|node_memory_MemAvailable_bytes|node_memory_MemFree_bytes|node_memory_MemTotal_bytes|node_memory_SReclaimable_bytes|node_memory_SUnreclaim_bytes|node_memory_ShmemHugePages_bytes|node_memory_ShmemPmdMapped_bytes|node_memory_Shmem_bytes|node_memory_Slab_bytes|node_memory_SwapTotal_bytes|node_memory_VmallocChunk_bytes|node_memory_VmallocTotal_bytes|node_memory_VmallocUsed_bytes|node_memory_WritebackTmp_bytes|node_memory_Writeback_bytes|node_netstat_Icmp6_InErrors|node_netstat_Icmp6_InMsgs|node_netstat_Icmp6_OutMsgs|node_netstat_Icmp_InErrors|node_netstat_Icmp_InMsgs|node_netstat_Icmp_OutMsgs|node_netstat_IpExt_InOctets|node_netstat_IpExt_OutOctets|node_netstat_TcpExt_ListenDrops|node_netstat_TcpExt_ListenOverflows|node_netstat_TcpExt_TCPSynRetrans|node_netstat_Tcp_InErrs|node_netstat_Tcp_InSegs|node_netstat_Tcp_OutRsts|node_netstat_Tcp_OutSegs|node_netstat_Tcp_RetransSegs|node_netstat_Udp6_InDatagrams|node_netstat_Udp6_InErrors|node_netstat_Udp6_NoPorts|node_netstat_Udp6_OutDatagrams|node_netstat_Udp6_RcvbufErrors|node_netstat_Udp6_SndbufErrors|node_netstat_UdpLite_InErrors|node_netstat_Udp_InDatagrams|node_netstat_Udp_InErrors|node_netstat_Udp_NoPorts|node_netstat_Udp_OutDatagrams|node_netstat_Udp_RcvbufErrors|node_netstat_Udp_SndbufErrors|node_network_carrier|node_network_info|node_network_mtu_bytes|node_network_receive_bytes_total|node_network_receive_compressed_total|node_network_receive_drop_total|node_network_receive_errs_total|node_network_receive_fifo_total|node_network_receive_multicast_total|node_network_receive_packets_total|node_network_speed_bytes|node_network_transmit_bytes_total|node_network_transmit_compressed_total|node_network_transmit_drop_total|node_network_transmit_errs_total|node_network_transmit_fifo_total|node_network_transmit_multicast_total|node_network_transmit_packets_total|node_network_transmit_queue_length|node_network_up|node_nf_conntrack_entries|node_nf_conntrack_entries_limit|node_os_info|node_sockstat_FRAG6_inuse|node_sockstat_FRAG_inuse|node_sockstat_RAW6_inuse|node_sockstat_RAW_inuse|node_sockstat_TCP6_inuse|node_sockstat_TCP_alloc|node_sockstat_TCP_inuse|node_sockstat_TCP_mem|node_sockstat_TCP_mem_bytes|node_sockstat_TCP_orphan|node_sockstat_TCP_tw|node_sockstat_UDP6_inuse|node_sockstat_UDPLITE6_inuse|node_sockstat_UDPLITE_inuse|node_sockstat_UDP_inuse|node_sockstat_UDP_mem|node_sockstat_UDP_mem_bytes|node_sockstat_sockets_used|node_softnet_dropped_total|node_softnet_processed_total|node_softnet_times_squeezed_total|node_systemd_unit_state|node_textfile_scrape_error|node_time_zone_offset_seconds|node_timex_estimated_error_seconds|node_timex_maxerror_seconds|node_timex_offset_seconds|node_timex_sync_status|node_uname_info|node_vmstat_oom_kill|node_vmstat_pgfault|node_vmstat_pgmajfault|node_vmstat_pgpgin|node_vmstat_pgpgout|node_vmstat_pswpin|node_vmstat_pswpout|process_max_fds|process_open_fds" - action = "keep" - } -} - -// DOCKER ------------ // - -// Host Cadvisor on the Docker socket to expose container metrics. -prometheus.exporter.cadvisor "linux" { - docker_only = true -} - -discovery.relabel "linux" { - targets = prometheus.exporter.cadvisor.linux.targets - - rule { - target_label = "job" - replacement = "integrations/docker" - } - - rule { - target_label = "instance" - replacement = local.hostname - } -} - -// Configure a prometheus.scrape component to collect cadvisor metrics. -prometheus.scrape "scraper" { - targets = discovery.relabel.linux.output - forward_to = [prometheus.remote_write.metrics_service.receiver] - - scrape_interval = local.scrape_interval +loki.source.docker "default" { + docker_host = "unix:///var/run/docker.sock" + labels = { + job = "docker-logs" + instance = local.hostname + } } -// ############################### -// #### Logging Configuration #### -// ############################### - -loki.write "grafana_cloud_loki" { - endpoint { - url = local.gc_loki_url +otelcol.processor.batch "default" {} - basic_auth { - username = local.gc_loki_id - password = local.gc_api_key - } - } +otelcol.exporter.prometheusremotewrite "grafana_cloud" { + endpoint = local.gc_prom_url + headers = { + "Authorization" = "Basic ${base64(local.gc_username + ":" + local.gc_api_key)}" + } } -// DOCKER ------------ // - -// Discover Docker containers and extract metadata. -discovery.docker "linux" { - host = "unix:///var/run/docker.sock" +loki.exporter "grafana_cloud" { + endpoint = local.gc_loki_url + labels = { + job = "container-logs" + hostname = local.hostname + } + tenant_id = local.gc_username + basic_auth { + username = local.gc_username + password = local.gc_api_key + } } -// Define a relabeling rule to create a service name from the container name. -discovery.relabel "logs_integrations_docker" { - targets = [] - - rule { - source_labels = ["__meta_docker_container_name"] - regex = "/(.*)" - target_label = "container_name" - } - - rule { - target_label = "instance" - replacement = local.hostname +otelcol.service "metrics" { + pipelines = { + metrics = { + receivers = [ + otelcol.receiver.hostmetrics.default, + otelcol.receiver.docker_stats.default, + ] + processors = [otelcol.processor.batch.default] + exporters = [otelcol.exporter.prometheusremotewrite.grafana_cloud] } + } } -// Configure a loki.source.docker component to collect logs from Docker containers. -loki.source.docker "default" { - host = "unix:///var/run/docker.sock" - targets = discovery.docker.linux.targets - relabel_rules = discovery.relabel.logs_integrations_docker.rules - forward_to = [loki.write.grafana_cloud_loki.receiver] +loki.service "logs" { + sources = [loki.source.docker.default] + exporters = [loki.exporter.grafana_cloud] }