1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use crate::server::{
    commands::Command,
    reply::{Reply, ReplyCode},
    Event, FTPErrorKind, InternalMsg,
};

use lazy_static::*;
use prometheus::{
    __register_counter_vec, __register_gauge, opts, register_counter, register_int_counter, register_int_counter_vec, register_int_gauge, IntCounter,
    IntCounterVec, IntGauge,
};

lazy_static! {
    static ref FTP_AUTH_FAILURES: IntCounter = register_int_counter!(opts!("ftp_auth_failures", "Total number of authentication failures.")).unwrap();
    static ref FTP_SESSIONS: IntGauge = register_int_gauge!(opts!("ftp_sessions_total", "Total number of FTP sessions.")).unwrap();
    static ref FTP_BACKEND_WRITE_BYTES: IntCounter =
        register_int_counter!(opts!("ftp_backend_write_bytes", "Total number of bytes written to the backend.")).unwrap();
    static ref FTP_BACKEND_READ_BYTES: IntCounter =
        register_int_counter!(opts!("ftp_backend_read_bytes", "Total number of bytes retrieved from the backend.")).unwrap();
    static ref FTP_BACKEND_WRITE_FILES: IntCounter =
        register_int_counter!(opts!("ftp_backend_write_files", "Total number of files written to the backend.")).unwrap();
    static ref FTP_BACKEND_READ_FILES: IntCounter =
        register_int_counter!(opts!("ftp_backend_read_files", "Total number of files retrieved from the backend.")).unwrap();
    static ref FTP_COMMAND_TOTAL: IntCounterVec = register_int_counter_vec!("ftp_command_total", "Total number of commands received.", &["command"]).unwrap();
    static ref FTP_REPLY_TOTAL: IntCounterVec =
        register_int_counter_vec!("ftp_reply_total", "Total number of reply codes server sent to clients.", &["range"]).unwrap();
    static ref FTP_ERROR_TOTAL: IntCounterVec = register_int_counter_vec!("ftp_error_total", "Total number of errors encountered.", &["type"]).unwrap();
}

/// Add a metric for an event.
pub fn add_event_metric(event: &Event) {
    match event {
        Event::Command(cmd) => {
            add_command_metric(&cmd);
        }
        Event::InternalMsg(msg) => match msg {
            InternalMsg::SendData { bytes } => {
                FTP_BACKEND_READ_BYTES.inc_by(*bytes);
                FTP_BACKEND_READ_FILES.inc();
            }
            InternalMsg::WrittenData { bytes } => {
                FTP_BACKEND_WRITE_BYTES.inc_by(*bytes);
                FTP_BACKEND_WRITE_FILES.inc();
            }
            _ => {}
        },
    }
}

/// Increase the metrics gauge for client sessions
pub fn inc_session() {
    FTP_SESSIONS.inc();
}

/// Decrease the metrics gauge for client sessions
pub fn dec_session() {
    FTP_SESSIONS.dec();
}

/// Add a metric for an FTP server error.
pub fn add_error_metric(error: &FTPErrorKind) {
    let error_str = error.to_string();
    let label = error_str.split_whitespace().nth(0).unwrap_or("unknown").to_lowercase();
    FTP_ERROR_TOTAL.with_label_values(&[&label]).inc();
}

fn add_command_metric(cmd: &Command) {
    let cmd_str = cmd.to_string();
    let label = cmd_str.split_whitespace().nth(0).unwrap_or("unknown").to_lowercase();
    FTP_COMMAND_TOTAL.with_label_values(&[&label]).inc();
}

/// Add a metric for a reply.
pub fn add_reply_metric(reply: &Reply) {
    match *reply {
        Reply::None => {}
        Reply::CodeAndMsg { code, .. } => add_replycode_metric(code),
        Reply::MultiLine { code, .. } => add_replycode_metric(code),
    }
}

fn add_replycode_metric(code: ReplyCode) {
    let range = format!("{}xx", code as u32 / 100 % 10);
    FTP_REPLY_TOTAL.with_label_values(&[&range]).inc();
}