Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ping.rs   Sprache: unbekannt

 
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use crate::ping::PingMaker;
use crate::upload::PingPayload;
use crate::Glean;

use uuid::Uuid;

/// Stores information about a ping.
///
/// This is required so that given metric data queued on disk we can send
/// pings with the correct settings, e.g. whether it has a client_id.
#[derive(Clone)]
pub struct PingType(Arc<InnerPing>);

struct InnerPing {
    /// The name of the ping.
    pub name: String,
    /// Whether the ping should include the client ID.
    pub include_client_id: bool,
    /// Whether the ping should be sent if it is empty
    pub send_if_empty: bool,
    /// Whether to use millisecond-precise start/end times.
    pub precise_timestamps: bool,
    /// Whether to include the {client|ping}_info sections on assembly.
    pub include_info_sections: bool,
    /// Whether this ping is enabled.
    pub enabled: AtomicBool,
    /// Other pings that should be scheduled when this ping is sent.
    pub schedules_pings: Vec<String>,
    /// The "reason" codes that this ping can send
    pub reason_codes: Vec<String>,

    /// True when it follows the `collection_enabled` flag (aka `upload_enabled`) flag.
    /// Otherwise it needs to be enabled through `enabled_pings`.
    follows_collection_enabled: AtomicBool,
}

impl fmt::Debug for PingType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PingType")
            .field("name", &self.0.name)
            .field("include_client_id", &self.0.include_client_id)
            .field("send_if_empty", &self.0.send_if_empty)
            .field("precise_timestamps", &self.0.precise_timestamps)
            .field("include_info_sections", &self.0.include_info_sections)
            .field("enabled", &self.0.enabled.load(Ordering::Relaxed))
            .field("schedules_pings", &self.0.schedules_pings)
            .field("reason_codes", &self.0.reason_codes)
            .field(
                "follows_collection_enabled",
                &self.0.follows_collection_enabled.load(Ordering::Relaxed),
            )
            .finish()
    }
}

// IMPORTANT:
//
// When changing this implementation, make sure all the operations are
// also declared in the related trait in `../traits/`.
impl PingType {
    /// Creates a new ping type for the given name, whether to include the client ID and whether to
    /// send this ping empty.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the ping.
    /// * `include_client_id` - Whether to include the client ID in the assembled ping when submitting.
    /// * `send_if_empty` - Whether the ping should be sent empty or not.
    /// * `precise_timestamps` - Whether the ping should use precise timestamps for the start and end time.
    /// * `include_info_sections` - Whether the ping should include the client/ping_info sections.
    /// * `enabled` - Whether or not this ping is enabled. Note: Data that would be sent on a disabled
    ///   ping will still be collected but is discarded rather than being submitted.
    /// * `reason_codes` - The valid reason codes for this ping.
    #[allow(clippy::too_many_arguments)]
    pub fn new<A: Into<String>>(
        name: A,
        include_client_id: bool,
        send_if_empty: bool,
        precise_timestamps: bool,
        include_info_sections: bool,
        enabled: bool,
        schedules_pings: Vec<String>,
        reason_codes: Vec<String>,
        follows_collection_enabled: bool,
    ) -> Self {
        Self::new_internal(
            name,
            include_client_id,
            send_if_empty,
            precise_timestamps,
            include_info_sections,
            enabled,
            schedules_pings,
            reason_codes,
            follows_collection_enabled,
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn new_internal<A: Into<String>>(
        name: A,
        include_client_id: bool,
        send_if_empty: bool,
        precise_timestamps: bool,
        include_info_sections: bool,
        enabled: bool,
        schedules_pings: Vec<String>,
        reason_codes: Vec<String>,
        follows_collection_enabled: bool,
    ) -> Self {
        let this = Self(Arc::new(InnerPing {
            name: name.into(),
            include_client_id,
            send_if_empty,
            precise_timestamps,
            include_info_sections,
            enabled: AtomicBool::new(enabled),
            schedules_pings,
            reason_codes,
            follows_collection_enabled: AtomicBool::new(follows_collection_enabled),
        }));

        // Register this ping.
        // That will happen asynchronously and not block operation.
        crate::register_ping_type(&this);

        this
    }

    pub(crate) fn name(&self) -> &str {
        &self.0.name
    }

    pub(crate) fn include_client_id(&self) -> bool {
        self.0.include_client_id
    }

    pub(crate) fn send_if_empty(&self) -> bool {
        self.0.send_if_empty
    }

    pub(crate) fn precise_timestamps(&self) -> bool {
        self.0.precise_timestamps
    }

    pub(crate) fn include_info_sections(&self) -> bool {
        self.0.include_info_sections
    }

    /// Enable or disable a ping.
    ///
    /// Disabling a ping causes all data for that ping to be removed from storage
    /// and all pending pings of that type to be deleted.
    pub fn set_enabled(&self, enabled: bool) {
        crate::set_ping_enabled(self, enabled)
    }

    /// Store whether this ping is enabled or not.
    ///
    /// **Note**: For internal use only. Only stores the flag. Does not touch any stored data.
    /// Use the public API `PingType::set_enabled` instead.
    pub(crate) fn store_enabled(&self, enabled: bool) {
        self.0.enabled.store(enabled, Ordering::Release);
    }

    pub(crate) fn enabled(&self, glean: &Glean) -> bool {
        if self.0.follows_collection_enabled.load(Ordering::Relaxed) {
            // if this follows collection_enabled:
            // 1. check that first. if disabled, we're done
            // 2. if enabled, check server-knobs
            // 3. If that is not set, fall-through checking the ping
            if !glean.is_upload_enabled() {
                return false;
            }

            let remote_settings_config = &glean.remote_settings_config.lock().unwrap();

            if !remote_settings_config.pings_enabled.is_empty() {
                if let Some(remote_enabled) = remote_settings_config.pings_enabled.get(self.name())
                {
                    return *remote_enabled;
                }
            }
        }

        self.0.enabled.load(Ordering::Relaxed)
    }

    pub(crate) fn follows_collection_enabled(&self) -> bool {
        self.0.follows_collection_enabled.load(Ordering::Relaxed)
    }

    pub(crate) fn schedules_pings(&self) -> &[String] {
        &self.0.schedules_pings
    }

    /// Submits the ping for eventual uploading.
    ///
    /// The ping content is assembled as soon as possible, but upload is not
    /// guaranteed to happen immediately, as that depends on the upload policies.
    ///
    /// If the ping currently contains no content, it will not be sent,
    /// unless it is configured to be sent if empty.
    ///
    /// # Arguments
    ///
    /// * `reason` - the reason the ping was triggered. Included in the
    ///   `ping_info.reason` part of the payload.
    pub fn submit(&self, reason: Option<String>) {
        let ping = PingType(Arc::clone(&self.0));

        // Need to separate access to the Glean object from access to global state.
        // `trigger_upload` itself might lock the Glean object and we need to avoid that deadlock.
        crate::dispatcher::launch(|| {
            let sent =
                crate::core::with_glean(move |glean| ping.submit_sync(glean, reason.as_deref()));
            if sent {
                let state = crate::global_state().lock().unwrap();
                if let Err(e) = state.callbacks.trigger_upload() {
                    log::error!("Triggering upload failed. Error: {}", e);
                }
            }
        })
    }

    /// Collects and submits a ping for eventual uploading.
    ///
    /// # Returns
    ///
    /// Whether the ping was succesfully assembled and queued.
    #[doc(hidden)]
    pub fn submit_sync(&self, glean: &Glean, reason: Option<&str>) -> bool {
        if !self.enabled(glean) {
            log::info!(
                "The ping '{}' is disabled and will be discarded and not submitted",
                self.0.name
            );

            return false;
        }

        let ping = &self.0;

        // Allowing `clippy::manual_filter`.
        // This causes a false positive.
        // We have a side-effect in the `else` branch,
        // so shouldn't delete it.
        #[allow(unknown_lints)]
        #[allow(clippy::manual_filter)]
        let corrected_reason = match reason {
            Some(reason) => {
                if ping.reason_codes.contains(&reason.to_string()) {
                    Some(reason)
                } else {
                    log::error!("Invalid reason code {} for ping {}", reason, ping.name);
                    None
                }
            }
            None => None,
        };

        let ping_maker = PingMaker::new();
        let doc_id = Uuid::new_v4().to_string();
        let url_path = glean.make_path(&ping.name, &doc_id);
        match ping_maker.collect(glean, self, corrected_reason, &doc_id, &url_path) {
            None => {
                log::info!(
                    "No content for ping '{}', therefore no ping queued.",
                    ping.name
                );
                false
            }
            Some(ping) => {
                if !self.enabled(glean) {
                    log::info!(
                        "The ping '{}' is disabled and will be discarded and not submitted",
                        ping.name
                    );

                    return false;
                }

                // This metric is recorded *after* the ping is collected (since
                // that is the only way to know *if* it will be submitted). The
                // implication of this is that the count for a metrics ping will
                // be included in the *next* metrics ping.
                glean
                    .additional_metrics
                    .pings_submitted
                    .get(ping.name)
                    .add_sync(glean, 1);

                if let Err(e) = ping_maker.store_ping(glean.get_data_path(), &ping) {
                    log::warn!(
                        "IO error while writing ping to file: {}. Enqueuing upload of what we have in memory.",
                        e
                    );
                    glean.additional_metrics.io_errors.add_sync(glean, 1);
                    // `serde_json::to_string` only fails if serialization of the content
                    // fails or it contains maps with non-string keys.
                    // However `ping.content` is already a `JsonValue`,
                    // so both scenarios should be impossible.
                    let content =
                        ::serde_json::to_string(&ping.content).expect("ping serialization failed");
                    // TODO: Shouldn't we consolidate on a single collected Ping representation?
                    let ping = PingPayload {
                        document_id: ping.doc_id.to_string(),
                        upload_path: ping.url_path.to_string(),
                        json_body: content,
                        headers: Some(ping.headers),
                        body_has_info_sections: self.0.include_info_sections,
                        ping_name: self.0.name.to_string(),
                    };

                    glean.upload_manager.enqueue_ping(glean, ping);
                    return true;
                }

                glean.upload_manager.enqueue_ping_from_file(glean, &doc_id);

                log::info!(
                    "The ping '{}' was submitted and will be sent as soon as possible",
                    ping.name
                );

                if ping.schedules_pings.is_empty() {
                    let ping_schedule = glean
                        .ping_schedule
                        .get(ping.name)
                        .map(|v| &v[..])
                        .unwrap_or(&[]);

                    if !ping_schedule.is_empty() {
                        log::info!(
                            "The ping '{}' is being used to schedule other pings: {:?}",
                            ping.name,
                            ping_schedule
                        );

                        for scheduled_ping_name in ping_schedule {
                            glean.submit_ping_by_name(scheduled_ping_name, reason);
                        }
                    }
                } else {
                    log::info!(
                        "The ping '{}' is being used to schedule other pings: {:?}",
                        ping.name,
                        ping.schedules_pings
                    );
                    for scheduled_ping_name in &ping.schedules_pings {
                        glean.submit_ping_by_name(scheduled_ping_name, reason);
                    }
                }

                true
            }
        }
    }
}

[ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge