3个版本 (1个稳定版本)

1.0.3 2024年8月21日
0.2.0 2024年8月15日
0.1.0 2024年8月15日

#90 in 机器学习

Download history 86/week @ 2024-08-09 252/week @ 2024-08-16

每月下载 338
用于 2 crate

GPL-2.0-only

49KB
1K SLoC

Rust 1K SLoC Python 119 SLoC // 0.2% comments

为sched_ext调度器提供统计传输库

sched_ext 是Linux内核特性,它使得在BPF中实现内核线程调度器并动态加载它们成为可能。

此库提供了一个简单的方法来定义统计信息并通过UNIX域套接字访问它们。虽然此库是为SCX调度器开发的,但它可以在其他地方使用,唯一的假设是默认的UNIX域套接字路径,该路径可以被覆盖。

统计信息定义为结构体。一个统计信息结构体可以包含以下字段

  • 数字 - i32, u32, i64, u64, f64。

  • 字符串。

  • 包含允许字段的结构体。

  • VecBTreeMap 包含上述内容。

以下内容来自 examples/stats_defs.rs.h

#[derive(Clone, Debug, Serialize, Deserialize, Stats)]
#[stat(desc = "domain statistics", _om_prefix="d_", _om_label="domain_name")]
struct DomainStats {
    pub name: String,
    #[stat(desc = "an event counter")]
    pub events: u64,
    #[stat(desc = "a gauge number")]
    pub pressure: f64,
}

#[derive(Clone, Debug, Serialize, Deserialize, Stats)]
#[stat(desc = "cluster statistics", top)]
struct ClusterStats {
    pub name: String,
    #[stat(desc = "update timestamp")]
    pub at: u64,
    #[stat(desc = "some bitmap we want to report", _om_skip)]
    pub bitmap: Vec<u32>,
    #[stat(desc = "domain statistics")]
    pub doms_dict: BTreeMap<usize, DomainStats>,
}

scx_stats_derive::Stats 是生成所需一切的 derive 宏,包括统计元数据。stat 结构体和字段属性允许添加注释。以下属性目前被定义

结构和字段属性

  • desc: 描述。

仅结构体属性

  • top: 标记默认报告的最顶层统计结构体。用于通用工具在处理元数据时找到起点。

此外,可以在结构和字段上添加以“_”开头的任意用户属性。它们收集到包含的结构体或字段的“user”字典中。当此类用户属性的值未指定时,默认赋值为字符串“true”。例如,scripts/scxstats_to_openmetrics.py 识别以下用户属性

  • _om_prefix: 将值加到字段名前形成唯一的OpenMetrics度量名称。

  • _om_label: 标签用于区分字典的不同成员。此字段属性指定字典字段的标签名称。

  • _om_skip:并非所有字段都适合翻译成OpenMetrics。这个无值字段属性标记了要跳过的字段。

examples/stats_defs.rs.h展示了如何使用上述属性。有关实际用法,请参阅scx_layered

请注意,scx_stats依赖于serdeserde_json,并且每个统计结构都必须派生SerializeDeserialize

通过UNIX域套接字提供上述结构的统计服务器可以启动如下:

    let _server = ScxStatsServer::new()
        .set_path(&path)
        .add_stats_meta(ClusterStats::meta())
        .add_stats_meta(DomainStats::meta())
        .add_stats("top", Box::new(move |_| stats.to_json()))
        .launch()
        .unwrap();

scx_stats::Meta::meta() trait函数由每个统计结构的scx_stats::Meta derive宏自动实现。将它们添加到统计服务器中,可以实现不需要统计结构定义的通用客户端,例如将统计传递到另一个框架,如OpenMetrics。

top是未指定特定目标时的默认统计报告,应始终添加到服务器中。闭包应返回serde_json::Value。请注意,scx_stats::ToJson自动将.to_json()添加到实现了scx_stats::Metaserde::Serialize的结构的结构体中。

上面的代码将启动监听于@path的统计服务器。请注意,当_server变量被丢弃时,服务器将关闭。客户端也非常简单。取自examples/client.rs

    let mut client = ScxStatsClient::new().set_path(path).connect().unwrap();

上面的代码创建了一个客户端实例。让我们查询统计信息

    let resp = client.request::<ClusterStats>("stat", vec![]);
    println!("{:#?}", &resp);

上面的代码等同于查询top目标

    println!("\n===== Requesting \"stat\" with \"target\"=\"top\":");
    let resp = client.request::<ClusterStats>("stat", vec![("target".into(), "top".into())]);
    println!("{:#?}", &resp);

如果将("args", BTreeMap<String, String>)作为@args向量的部分传递,则将BTreeMap作为参数传递到服务器端的处理闭包。

当实现没有访问统计结构定义的通用客户端时,元数据会很有用

    println!("\n===== Requesting \"stats_meta\" but receiving with serde_json::Value:");
    let resp = client.request::<serde_json::Value>("stats_meta", vec![]).unwrap();
    println!("{}", serde_json::to_string_pretty(&resp).unwrap());

对于此示例,输出将如下所示

{
  "ClusterStats": {
    "desc": "cluster statistics",
    "fields": {
      "at": {
        "datum": "u64",
        "desc": "update timestamp"
      },
      "bitmap": {
        "array": "u64",
        "desc": "some bitmap we want to report",
        "user": {
          "_om_skip": "true"
        }
      },
      "doms_dict": {
        "desc": "domain statistics",
        "dict": {
          "datum": {
            "struct": "DomainStats"
          },
          "key": "u64"
        }
      },
      "name": {
        "datum": "string"
      }
    },
    "name": "ClusterStats",
    "top": "true"
  },
  "DomainStats": {
    "desc": "domain statistics",
    "fields": {
      "events": {
        "datum": "u64",
        "desc": "an event counter"
      },
      "name": {
        "datum": "string"
      },
      "pressure": {
        "datum": "float",
        "desc": "a gauge number"
      }
    },
    "name": "DomainStats",
    "user": {
      "_om_label": "domain_name",
      "_om_prefix": "d_"
    }
  }
}

UNIX域套接字上用于通信的协议是基于行的,每行包含一个json,非常简单。使用examples/client运行,并将RUST_LOG=trace设置为查看通过线缆发送的内容

> cargo run --example server -- ~/tmp/socket
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/examples/server /home/htejun/tmp/socket`
Server listening. Run `client "/home/htejun/tmp/socket"`.
Use `socat - UNIX-CONNECT:"/home/htejun/tmp/socket"` for raw connection.
Press any key to exit.
$ RUST_LOG=trace cargo run --example client -- ~/tmp/socket
...
===== Requesting "stats" but receiving with serde_json::Value:
2024-08-15T22:13:23.769Z TRACE [scx_stats::client] Sending: {"req":"stats","args":{"target":"top"}}
2024-08-15T22:13:23.769Z TRACE [scx_stats::client] Received: {"errno":0,"args":{"resp":{"at":12345,"bitmap":[3735928559,3203391149],"doms_dict":{"0":{"events":1234,"name":"domain 0","pressure":1.234},"3":{"events":5678,"name":"domain 3","pressure":5.678}},"name":"test cluster"}}}
Ok(
    Object {
        "at": Number(12345),
        "bitmap": Array [
            Number(3735928559),
            Number(3203391149),
        ],
        "doms_dict": Object {
            "0": Object {
                "events": Number(1234),
                "name": String("domain 0"),
                "pressure": Number(1.234),
            },
            "3": Object {
                "events": Number(5678),
                "name": String("domain 3"),
                "pressure": Number(5.678),
            },
        },
        "name": String("test cluster"),
    },

依赖项

~1-2MB
~41K SLoC