Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rustdoc-args = ["--cfg", "docsrs"]
nom = "7.1.3"
mac_address = { version = "1.1.5", features = ["serde"] }
serde = { version = "1.0.166", features = ["derive"] }
uuid = { version = "1.21.0", features = ["serde"] }

[dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] }
Expand Down
44 changes: 40 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ An sFlow v5 parser library written in Rust. Parses sFlow v5 datagrams ([sFlow v5

- **sFlow v5** datagram parsing with IPv4 and IPv6 agent addresses
- **All four sample types**: Flow Sample, Counter Sample, Expanded Flow Sample, Expanded Counter Sample
- **Flow record types**: Raw Packet Header, Sampled Ethernet, Sampled IPv4/IPv6, Extended Switch, Extended Router, Extended Gateway, Extended User, Extended URL
- **Counter record types**: Generic Interface, Ethernet Interface, Token Ring, VLAN, Processor
- **38 flow record types**: Raw Packet Header, Sampled Ethernet, Sampled IPv4/IPv6, Extended Switch/Router/Gateway/User/URL, Extended MPLS (tunnel, VC, FTN, LDP FEC), Extended NAT, Extended VLAN Tunnel, Extended 802.11 (payload, Rx, Tx), Extended L2/IPv4/IPv6 Tunnel (egress/ingress), Extended Decapsulate/VNI (egress/ingress), Extended Egress Queue/ACL/Function/Transit/Queue, Extended Socket IPv4/IPv6, Extended Proxy Socket IPv4/IPv6, JVM Runtime, Memcache Operation, App Operation, HTTP Request, Extended Proxy Request
- **43 counter record types**: Generic/Ethernet/Token Ring/VG/VLAN/802.11/LAG/SFP interface counters, Slow Path Counts, InfiniBand, Processor, Queue Length, Radio Utilization, OpenFlow Port, Port Name, Host Description/Adapters/Parent/CPU/Memory/Disk IO/Net IO, Virtual Node/CPU/Memory/Disk IO/Net IO, MIB-II IP/ICMP/TCP/UDP, JMX Runtime, JVM Statistics, HTTP Counters, App Operations/Resources/Workers, Memcache Counters, OVS Datapath Stats, Energy/Temperature/Humidity/Fans, XenServer VIF (enterprise 4300)
- **Unknown record handling**: Unrecognized records captured as raw bytes for forward compatibility
- **Serde support**: All types serialize/deserialize to JSON and other formats
- **Builder pattern**: Optional configuration (e.g., max samples limit for DoS protection)
Expand Down Expand Up @@ -120,14 +120,50 @@ Datagram
│ ├── Extended Router (0:1002)
│ ├── Extended Gateway (0:1003)
│ ├── Extended User (0:1004)
│ └── Extended URL (0:1005)
│ ├── Extended URL (0:1005)
│ ├── Extended MPLS (0:1006)
│ ├── Extended NAT (0:1007)
│ ├── Extended MPLS Tunnel/VC/FTN/LDP FEC (0:1008–1011)
│ ├── Extended VLAN Tunnel (0:1012)
│ ├── Extended 802.11 Payload/Rx/Tx (0:1013–1015)
│ ├── Extended L2/IPv4/IPv6 Tunnel Egress/Ingress (0:1021–1026)
│ ├── Extended Decapsulate/VNI Egress/Ingress (0:1027–1030)
│ ├── Extended Egress Queue/ACL/Function/Transit/Queue (0:1036–1040)
│ ├── Extended Socket IPv4/IPv6 (0:2100–2101)
│ ├── Extended Proxy Socket IPv4/IPv6 (0:2102–2103)
│ ├── JVM Runtime (0:2105)
│ ├── Memcache Operation (0:2200)
│ ├── App Operation (0:2202)
│ ├── HTTP Request (0:2206)
│ └── Extended Proxy Request (0:2207)
├── Counter Sample (enterprise=0, format=2)
│ └── Counter Records[]
│ ├── Generic Interface (0:1)
│ ├── Ethernet Interface (0:2)
│ ├── Token Ring (0:3)
│ ├── VG Counters (0:4)
│ ├── VLAN (0:5)
│ └── Processor (0:1001)
│ ├── IEEE 802.11 Counters (0:6)
│ ├── LAG Port Stats (0:7)
│ ├── Slow Path Counts (0:8)
│ ├── InfiniBand Counters (0:9)
│ ├── SFP/Optical (0:10)
│ ├── Processor (0:1001)
│ ├── Radio Utilization (0:1002)
│ ├── Queue Length (0:1003)
│ ├── OpenFlow Port (0:1004)
│ ├── Port Name (0:1005)
│ ├── Host Descr/Adapters/Parent/CPU/Memory/Disk IO/Net IO (0:2000–2006)
│ ├── Virtual Node/CPU/Memory/Disk IO/Net IO (0:2100–2104)
│ ├── MIB-II IP/ICMP/TCP/UDP (0:2007–2010)
│ ├── JMX Runtime (0:2105)
│ ├── JVM Statistics (0:2106)
│ ├── HTTP Counters (0:2201)
│ ├── App Operations/Resources/Workers (0:2202–2206)
│ ├── Memcache Counters (0:2204)
│ ├── OVS Datapath Stats (0:2207)
│ ├── Energy/Temperature/Humidity/Fans (0:3000–3003)
│ └── XenServer VIF (4300:2)
├── Expanded Flow Sample (enterprise=0, format=3)
└── Expanded Counter Sample (enterprise=0, format=4)
```
Expand Down
22 changes: 19 additions & 3 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

- **`SflowError::Incomplete`**: `context` field changed from `String` to `ParseContext` enum; added `expected: Option<usize>` field
- **`SflowError::ParseError`**: `context` field changed from `String` to `ParseContext` enum; `kind` field changed from `String` to `ParseErrorKind` enum
- **`HostDescr::uuid`**: field type changed from `[u8; 16]` to `uuid::Uuid`
- **`HostDescr::machine_type`**: field type changed from `u32` to `MachineType` enum
- **`HostDescr::os_name`**: field type changed from `u32` to `OsName` enum
- **`HostCpu`**: `load_one`, `load_five`, `load_fifteen` changed from `u32` to `f32`
- **`VirtCpu::state`**: field type changed from `u32` to `VirtDomainState` enum
- **`Eq` removed** from `HostCpu`, `CounterRecord`, `CounterSample`, `ExpandedCounterSample`, `SflowSample`, `SflowDatagram`, `ParseResult` (due to `f32` fields; `PartialEq` is preserved)

### Added

Expand All @@ -16,21 +22,31 @@
- Queue, ACL, function, transit (formats 1036–1040): `ExtendedEgressQueue`, `ExtendedAcl`, `ExtendedFunction`, `ExtendedTransit`, `ExtendedQueue`
- Socket (formats 2100–2103): `ExtendedSocketIpv4`, `ExtendedSocketIpv6`, `ExtendedProxySocketIpv4`, `ExtendedProxySocketIpv6`
- Application & JVM (formats 2105, 2200, 2202, 2206, 2207): `JvmRuntime`, `MemcacheOperation`, `AppOperation`, `HttpRequest`, `ExtendedProxyRequest`
- **25 new counter record types** (enterprise=0):
- **40 new counter record types**:
- Core (formats 4, 6, 7, 10): `VgCounters`, `Ieee80211Counters`, `LagPortStats`, `Sfp`
- Slow path & InfiniBand (formats 8, 9): `SlowPathCounts`, `IbCounters`
- OpenFlow & radio (formats 1002, 1004, 1005): `RadioUtilization`, `OfPort`, `PortName`
- Host monitoring (formats 2000–2010): `HostDescr`, `HostAdapters`, `HostParent`, `HostCpu`, `HostMemory`, `HostDiskIo`, `HostNetIo`, `Mib2IpGroup`, `Mib2IcmpGroup`, `Mib2TcpGroup`, `Mib2UdpGroup`
- Application & JVM (formats 2106, 2201–2204, 2206): `JvmStatistics`, `HttpCounters`, `AppOperations`, `AppResources`, `MemcacheCounters`, `AppWorkers`
- Queue length (format 1003): `QueueLength`
- Host monitoring (formats 2000–2006): `HostDescr`, `HostAdapters`, `HostParent`, `HostCpu`, `HostMemory`, `HostDiskIo`, `HostNetIo`
- Virtual machine / hypervisor (formats 2100–2104): `VirtNode`, `VirtCpu`, `VirtMemory`, `VirtDiskIo`, `VirtNetIo`
- MIB-II (formats 2007–2010): `Mib2IpGroup`, `Mib2IcmpGroup`, `Mib2TcpGroup`, `Mib2UdpGroup`
- JVM (formats 2105–2106): `JmxRuntime`, `JvmStatistics`
- Application & HTTP (formats 2201–2206): `HttpCounters`, `AppOperations`, `AppResources`, `MemcacheCounters`, `AppWorkers`
- Open vSwitch (format 2207): `OvsDpStats`
- Environmental (formats 3000–3003): `Energy`, `Temperature`, `Humidity`, `Fans`
- XenServer VIF (enterprise=4300, format=2): `XenVif`
- `ParseContext` enum with 14 variants covering all parsing phases (e.g., `DatagramHeader`, `AgentAddress`, `FlowSample`)
- `ParseErrorKind` enum with `InvalidAddressType` and `NomError(nom::error::ErrorKind)` variants
- `expected: Option<usize>` field on `SflowError::Incomplete` for cases where the required byte count is known
- `uuid` crate dependency for proper UUID representation in `HostDescr`

### Improved

- Error paths no longer allocate on the heap (enums are `Copy` instead of `String`)
- Consumers can exhaustively match on error contexts and kinds
- Display output remains compatible with previous format
- Records previously parsed as `Unknown` (e.g., formats 2100, 2200, 1029, 1030) are now fully decoded
- `HostDescr.uuid` field changed from `[u8; 16]` to `uuid::Uuid` for proper formatting and serialization

## 0.1.1

Expand Down
40 changes: 40 additions & 0 deletions src/counter_records/energy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use nom::IResult;
use nom::number::complete::be_u32;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Energy {
/// Voltage in millivolts.
pub voltage: u32,
/// Current in milliamps.
pub current: u32,
/// Real power in milliwatts.
pub real_power: u32,
/// Power factor percentage (0 for DC).
pub power_factor: u32,
/// Energy consumed in millijoules.
pub energy: u32,
/// Error count.
pub errors: u32,
}

pub(crate) fn parse_energy(input: &[u8]) -> IResult<&[u8], Energy> {
let (input, voltage) = be_u32(input)?;
let (input, current) = be_u32(input)?;
let (input, real_power) = be_u32(input)?;
let (input, power_factor) = be_u32(input)?;
let (input, energy) = be_u32(input)?;
let (input, errors) = be_u32(input)?;

Ok((
input,
Energy {
voltage,
current,
real_power,
power_factor,
energy,
errors,
},
))
}
28 changes: 28 additions & 0 deletions src/counter_records/fans.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use nom::IResult;
use nom::number::complete::be_u32;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fans {
/// Total number of fans.
pub total: u32,
/// Number of failed fans.
pub failed: u32,
/// Fan speed (RPM or percentage).
pub speed: u32,
}

pub(crate) fn parse_fans(input: &[u8]) -> IResult<&[u8], Fans> {
let (input, total) = be_u32(input)?;
let (input, failed) = be_u32(input)?;
let (input, speed) = be_u32(input)?;

Ok((
input,
Fans {
total,
failed,
speed,
},
))
}
22 changes: 11 additions & 11 deletions src/counter_records/host_cpu.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use nom::IResult;
use nom::number::complete::be_u32;
use nom::number::complete::{be_f32, be_u32};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HostCpu {
/// 1-minute load average as IEEE 754 float bits. Use `f32::from_bits()` to convert.
pub load_one: u32,
/// 5-minute load average as IEEE 754 float bits. Use `f32::from_bits()` to convert.
pub load_five: u32,
/// 15-minute load average as IEEE 754 float bits. Use `f32::from_bits()` to convert.
pub load_fifteen: u32,
/// 1-minute load average.
pub load_one: f32,
/// 5-minute load average.
pub load_five: f32,
/// 15-minute load average.
pub load_fifteen: f32,
pub proc_run: u32,
pub proc_total: u32,
pub cpu_num: u32,
Expand All @@ -27,9 +27,9 @@ pub struct HostCpu {
}

pub(crate) fn parse_host_cpu(input: &[u8]) -> IResult<&[u8], HostCpu> {
let (input, load_one) = be_u32(input)?;
let (input, load_five) = be_u32(input)?;
let (input, load_fifteen) = be_u32(input)?;
let (input, load_one) = be_f32(input)?;
let (input, load_five) = be_f32(input)?;
let (input, load_fifteen) = be_f32(input)?;
let (input, proc_run) = be_u32(input)?;
let (input, proc_total) = be_u32(input)?;
let (input, cpu_num) = be_u32(input)?;
Expand Down
94 changes: 87 additions & 7 deletions src/counter_records/host_descr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,103 @@ use nom::IResult;
use nom::bytes::complete::take;
use nom::number::complete::be_u32;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::flow_records::parse_sflow_string;

/// Machine architecture type from the sFlow host structures specification.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MachineType {
Unknown,
Other,
X86,
X86_64,
Ia64,
Sparc,
Alpha,
Mips,
PowerPc,
M68k,
Arm,
HpPa,
S390,
Unrecognized(u32),
}

impl From<u32> for MachineType {
fn from(v: u32) -> Self {
match v {
0 => MachineType::Unknown,
1 => MachineType::Other,
2 => MachineType::X86,
3 => MachineType::X86_64,
4 => MachineType::Ia64,
5 => MachineType::Sparc,
6 => MachineType::Alpha,
7 => MachineType::Mips,
8 => MachineType::PowerPc,
9 => MachineType::M68k,
10 => MachineType::Arm,
11 => MachineType::HpPa,
12 => MachineType::S390,
v => MachineType::Unrecognized(v),
}
}
}

/// Operating system name from the sFlow host structures specification.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OsName {
Unknown,
Other,
Linux,
Windows,
Darwin,
HpUx,
Aix,
DragonflyBsd,
FreeBsd,
NetBsd,
OpenBsd,
Osf,
Solaris,
Unrecognized(u32),
}

impl From<u32> for OsName {
fn from(v: u32) -> Self {
match v {
0 => OsName::Unknown,
1 => OsName::Other,
2 => OsName::Linux,
3 => OsName::Windows,
4 => OsName::Darwin,
5 => OsName::HpUx,
6 => OsName::Aix,
7 => OsName::DragonflyBsd,
8 => OsName::FreeBsd,
9 => OsName::NetBsd,
10 => OsName::OpenBsd,
11 => OsName::Osf,
12 => OsName::Solaris,
v => OsName::Unrecognized(v),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HostDescr {
pub hostname: String,
pub uuid: [u8; 16],
pub machine_type: u32,
pub os_name: u32,
pub uuid: Uuid,
pub machine_type: MachineType,
pub os_name: OsName,
pub os_release: String,
}

pub(crate) fn parse_host_descr(input: &[u8]) -> IResult<&[u8], HostDescr> {
let (input, hostname) = parse_sflow_string(input)?;
let (input, uuid_bytes) = take(16u8)(input)?;
let mut uuid = [0u8; 16];
uuid.copy_from_slice(uuid_bytes);
let uuid = Uuid::from_slice(uuid_bytes).unwrap_or(Uuid::nil());
let (input, machine_type) = be_u32(input)?;
let (input, os_name) = be_u32(input)?;
let (input, os_release) = parse_sflow_string(input)?;
Expand All @@ -28,8 +108,8 @@ pub(crate) fn parse_host_descr(input: &[u8]) -> IResult<&[u8], HostDescr> {
HostDescr {
hostname,
uuid,
machine_type,
os_name,
machine_type: MachineType::from(machine_type),
os_name: OsName::from(os_name),
os_release,
},
))
Expand Down
15 changes: 15 additions & 0 deletions src/counter_records/humidity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use nom::IResult;
use nom::number::complete::be_u32;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Humidity {
/// Relative humidity percentage.
pub relative_humidity: u32,
}

pub(crate) fn parse_humidity(input: &[u8]) -> IResult<&[u8], Humidity> {
let (input, relative_humidity) = be_u32(input)?;

Ok((input, Humidity { relative_humidity }))
}
Loading
Loading