Skip to content

Commit 61fbe31

Browse files
committed
API: Introduce a new link/protocol metrics tracker
This patch exposes some metrics from LibOSDP which can be used to gauge the quality of RS485 link and the state of the protocol for each PD. These are intended to be used by products like OsprioMini and OsprioPro will extract this data, mix it with other system metrics which are then used create a dashboards which allow deployment health monitoring. Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
1 parent 71556a6 commit 61fbe31

16 files changed

Lines changed: 293 additions & 1 deletion

File tree

configure.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ fi
170170

171171
## Declare sources
172172
LIBOSDP_SOURCES+=" src/osdp_common.c src/osdp_phy.c src/osdp_sc.c src/osdp_file.c src/osdp_pd.c"
173-
LIBOSDP_SOURCES+=" src/osdp_cp.c"
173+
LIBOSDP_SOURCES+=" src/osdp_cp.c src/osdp_metrics.c"
174174
LIBOSDP_SOURCES+=" utils/src/list.c utils/src/queue.c utils/src/utils.c"
175175
LIBOSDP_SOURCES+=" utils/src/disjoint_set.c utils/src/crc16.c"
176176
if [[ -z "${LOG_MINIMAL}" ]]; then
@@ -187,6 +187,7 @@ TARGETS="cp_app pd_app"
187187

188188
TEST_SOURCES="tests/unit-tests/test.c"
189189
TEST_SOURCES+=" tests/unit-tests/test-cp-phy.c"
190+
TEST_SOURCES+=" tests/unit-tests/test-pd-phy.c"
190191
TEST_SOURCES+=" tests/unit-tests/test-commands.c"
191192
TEST_SOURCES+=" tests/unit-tests/test-events.c"
192193
TEST_SOURCES+=" tests/unit-tests/test-cp-fsm.c"

include/osdp.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,68 @@ void osdp_get_status_mask(const osdp_t *ctx, uint8_t *bitmask);
14551455
OSDP_EXPORT
14561456
void osdp_get_sc_status_mask(const osdp_t *ctx, uint8_t *bitmask);
14571457

1458+
/**
1459+
* @brief Link/protocol health counters accumulated since the last
1460+
* @ref osdp_get_metrics() call.
1461+
*
1462+
* All counters saturate at their maximum value (no wraparound) and
1463+
* are reset to zero atomically when @ref osdp_get_metrics() returns
1464+
* a snapshot, giving callers interval-delta semantics without
1465+
* requiring subtraction.
1466+
*/
1467+
struct osdp_metrics {
1468+
/**
1469+
* Packets transmitted successfully on the wire. Counted once
1470+
* per @ref osdp_phy_send_packet() that reached the channel
1471+
* driver with the full payload.
1472+
*/
1473+
uint32_t packets_sent;
1474+
/**
1475+
* Packets received with a well-formed frame. Frames that failed
1476+
* the CRC/checksum integrity check are still counted here; only
1477+
* frames rejected earlier (bad SOM, bad length, bad direction
1478+
* bit, etc.) are excluded.
1479+
*/
1480+
uint32_t packets_received;
1481+
/**
1482+
* Inbound frames rejected at the integrity-check stage. Merged
1483+
* counter across CRC-16 and single-byte checksum failures — the
1484+
* check used is implicit in the negotiated capability.
1485+
*/
1486+
uint32_t packet_check_errors;
1487+
/**
1488+
* REPLY_NAK packets observed on this context. On a PD-mode
1489+
* context these are NAKs transmitted; on a CP-mode context
1490+
* these are NAKs received. Direction is implicit from the role.
1491+
*/
1492+
uint32_t nak_count;
1493+
/** Successful secure-channel activations (post-SCRYPT). */
1494+
uint32_t sc_handshake_count;
1495+
/** Secure-channel tear-downs of a previously active session. */
1496+
uint32_t sc_failure_count;
1497+
/** Commands processed at the application callback boundary. */
1498+
uint32_t command_count;
1499+
/** Events dispatched to the application callback. */
1500+
uint32_t event_count;
1501+
};
1502+
1503+
/**
1504+
* @brief Read and reset link/protocol health counters for one PD slot.
1505+
*
1506+
* Counters are tracked per-PD. In PD mode there is only one slot
1507+
* (pd_idx == 0). In CP mode pass the index of the downstream PD to
1508+
* snapshot; callers typically iterate 0..NUM_PD-1.
1509+
*
1510+
* @param ctx OSDP context
1511+
* @param pd_idx PD index to snapshot (0..NUM_PD-1)
1512+
* @param out Destination struct filled with the current counter values.
1513+
* The counters for this PD are then cleared to zero.
1514+
*
1515+
* @retval 0 on success, -1 on invalid arguments.
1516+
*/
1517+
OSDP_EXPORT
1518+
int osdp_get_metrics(osdp_t *ctx, int pd_idx, struct osdp_metrics *out);
1519+
14581520
/**
14591521
* @brief Open a pre-agreed file
14601522
*

include/osdp.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ class OSDP_EXPORT Common {
6262
return osdp_get_file_tx_status(_ctx, pd, size, offset);
6363
}
6464

65+
int get_metrics(int pd, struct osdp_metrics *metrics)
66+
{
67+
return osdp_get_metrics(_ctx, pd, metrics);
68+
}
69+
6570
protected:
6671
osdp_t *_ctx;
6772
};

python/osdp/control_panel.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ def get_file_tx_status(self, address):
194194
with self.lock:
195195
return self.ctx.get_file_tx_status(pd)
196196

197+
def get_metrics(self, address):
198+
pd = self.pd_addr.index(address)
199+
with self.lock:
200+
return self.ctx.get_metrics(pd)
201+
197202
def start(self):
198203
if self.thread:
199204
raise RuntimeError("Thread already running!")

python/osdp/peripheral_device.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ def get_file_tx_status(self):
123123
with self.lock:
124124
return self.ctx.get_file_tx_status(0)
125125

126+
def get_metrics(self):
127+
with self.lock:
128+
return self.ctx.get_metrics(0)
129+
126130
def stop(self):
127131
if not self.thread:
128132
raise RuntimeError("Thread not running!")

python/osdp_sys/base.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,46 @@ static PyObject *pyosdp_get_file_tx_status(pyosdp_base_t *self, PyObject *args)
138138
return dict;
139139
}
140140

141+
#define pyosdp_get_metrics_doc \
142+
"Get and reset link/protocol metrics for a PD slot\n" \
143+
"\n" \
144+
"@return dictionary with metric counters or None on invalid PD index.\n"
145+
static PyObject *pyosdp_get_metrics(pyosdp_base_t *self, PyObject *args)
146+
{
147+
int pd_idx;
148+
osdp_t *ctx;
149+
struct osdp_metrics metrics;
150+
PyObject *dict;
151+
pyosdp_cp_t *cp = (pyosdp_cp_t *)self;
152+
pyosdp_pd_t *pd = (pyosdp_pd_t *)self;
153+
154+
ctx = self->is_cp ? cp->ctx : pd->ctx;
155+
156+
if (!PyArg_ParseTuple(args, "I", &pd_idx))
157+
Py_RETURN_NONE;
158+
159+
if (osdp_get_metrics(ctx, pd_idx, &metrics))
160+
Py_RETURN_NONE;
161+
162+
dict = PyDict_New();
163+
if (dict == NULL)
164+
Py_RETURN_NONE;
165+
166+
if (pyosdp_dict_add_int(dict, "packets_sent", metrics.packets_sent) ||
167+
pyosdp_dict_add_int(dict, "packets_received", metrics.packets_received) ||
168+
pyosdp_dict_add_int(dict, "packet_check_errors", metrics.packet_check_errors) ||
169+
pyosdp_dict_add_int(dict, "nak_count", metrics.nak_count) ||
170+
pyosdp_dict_add_int(dict, "sc_handshake_count", metrics.sc_handshake_count) ||
171+
pyosdp_dict_add_int(dict, "sc_failure_count", metrics.sc_failure_count) ||
172+
pyosdp_dict_add_int(dict, "command_count", metrics.command_count) ||
173+
pyosdp_dict_add_int(dict, "event_count", metrics.event_count)) {
174+
Py_DECREF(dict);
175+
Py_RETURN_NONE;
176+
}
177+
178+
return dict;
179+
}
180+
141181
#define pyosdp_file_register_ops_doc \
142182
"Register file OPs handler\n" \
143183
"\n" \
@@ -261,6 +301,8 @@ static PyMethodDef pyosdp_base_methods[] = {
261301
pyosdp_file_register_ops_doc },
262302
{ "get_file_tx_status", (PyCFunction)pyosdp_get_file_tx_status, METH_VARARGS,
263303
pyosdp_file_tx_status_doc },
304+
{ "get_metrics", (PyCFunction)pyosdp_get_metrics, METH_VARARGS,
305+
pyosdp_get_metrics_doc },
264306
{ NULL } /* Sentinel */
265307
};
266308

python/setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def try_vendor_sources(src_dir, src_files, vendor_dir):
132132
"src/osdp_file.c",
133133
"src/osdp_pd.c",
134134
"src/osdp_cp.c",
135+
"src/osdp_metrics.c",
135136
"src/crypto/tinyaes_src.c",
136137
"src/crypto/tinyaes.c",
137138
]
@@ -141,6 +142,7 @@ def try_vendor_sources(src_dir, src_files, vendor_dir):
141142
"include/osdp_export.h",
142143
"src/osdp_common.h",
143144
"src/osdp_file.h",
145+
"src/osdp_metrics.h",
144146
"src/crypto/tinyaes_src.h",
145147
]
146148

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ list(APPEND LIB_OSDP_SOURCES
5656
${CMAKE_CURRENT_SOURCE_DIR}/osdp_cp.c
5757
${CMAKE_CURRENT_SOURCE_DIR}/osdp_sc.c
5858
${CMAKE_CURRENT_SOURCE_DIR}/osdp_file.c
59+
${CMAKE_CURRENT_SOURCE_DIR}/osdp_metrics.c
5960
)
6061

6162
if (OPT_OSDP_PACKET_TRACE OR OPT_OSDP_DATA_TRACE)

src/osdp_common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <assert.h>
1111
#include <stddef.h>
1212
#include <stdbool.h>
13+
#include <stdint.h>
1314
#include <string.h>
1415
#include <stdio.h>
1516
#include <stdlib.h>
@@ -463,6 +464,7 @@ struct osdp_pd {
463464

464465
struct osdp_secure_channel sc; /* Secure Channel session context */
465466
struct osdp_file *file; /* File transfer context */
467+
struct osdp_metrics metrics; /* link/protocol health counters */
466468

467469
/* PD command callback to app with opaque arg pointer as passed by app */
468470
void *command_callback_arg;

src/osdp_cp.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "osdp_common.h"
1010
#include "osdp_file.h"
1111
#include "osdp_diag.h"
12+
#include "osdp_metrics.h"
1213

1314
#define CMD_POLL_LEN 1
1415
#define CMD_LSTAT_LEN 1
@@ -62,6 +63,7 @@ static void cp_dispatch_event(struct osdp_pd *pd,
6263
if (ctx->event_callback) {
6364
ctx->event_callback(ctx->event_callback_arg, pd->idx,
6465
(struct osdp_event *)event);
66+
osdp_metrics_report(pd, OSDP_METRIC_EVENT);
6567
}
6668
}
6769

@@ -380,6 +382,7 @@ static int cp_decode_response(struct osdp_pd *pd, uint8_t *buf, int len)
380382
if (len != REPLY_NAK_DATA_LEN) {
381383
break;
382384
}
385+
osdp_metrics_report(pd, OSDP_METRIC_NAK);
383386
if (buf[pos] == OSDP_PD_NAK_MSG_CHK &&
384387
ISSET_FLAG(pd, PD_FLAG_CP_USE_CRC)) {
385388
LOG_INF("PD NAK'd CRC-16, falling back to checksum");
@@ -595,6 +598,7 @@ static int cp_decode_response(struct osdp_pd *pd, uint8_t *buf, int len)
595598
osdp_compute_session_keys(pd);
596599
if (osdp_verify_pd_cryptogram(pd) != 0) {
597600
LOG_ERR("Failed to verify PD cryptogram");
601+
osdp_metrics_report(pd, OSDP_METRIC_SC_FAILURE);
598602
return OSDP_CP_ERR_APP;
599603
}
600604
ret = OSDP_CP_ERR_NONE;
@@ -667,6 +671,7 @@ static int cp_build_and_send_packet(struct osdp_pd *pd)
667671
return OSDP_CP_ERR_GENERIC;
668672
}
669673

674+
osdp_metrics_report(pd, OSDP_METRIC_COMMAND);
670675
return OSDP_CP_ERR_NONE;
671676
}
672677

@@ -954,6 +959,7 @@ static void notify_pd_status(struct osdp_pd *pd, bool is_online)
954959
evt.notif.type = OSDP_EVENT_NOTIFICATION_PD_STATUS;
955960
evt.notif.arg0 = is_online;
956961
ctx->event_callback(ctx->event_callback_arg, pd->idx, &evt);
962+
osdp_metrics_report(pd, OSDP_METRIC_EVENT);
957963
}
958964

959965
static void notify_sc_status(struct osdp_pd *pd)
@@ -970,6 +976,7 @@ static void notify_sc_status(struct osdp_pd *pd)
970976
evt.notif.arg0 = sc_is_active(pd);
971977
evt.notif.arg1 = sc_use_scbkd(pd);
972978
ctx->event_callback(ctx->event_callback_arg, pd->idx, &evt);
979+
osdp_metrics_report(pd, OSDP_METRIC_EVENT);
973980
}
974981

975982
static void cp_keyset_complete(struct osdp_pd *pd)
@@ -1097,6 +1104,7 @@ static enum osdp_cp_state_e get_next_ok_state(struct osdp_pd *pd)
10971104
}
10981105
return OSDP_CP_STATE_ONLINE;
10991106
case OSDP_CP_STATE_SC_CHLNG:
1107+
osdp_metrics_report(pd, OSDP_METRIC_SC_HANDSHAKE);
11001108
return OSDP_CP_STATE_SC_SCRYPT;
11011109
case OSDP_CP_STATE_SC_SCRYPT:
11021110
sc_activate(pd);
@@ -1271,6 +1279,7 @@ static void notify_command_status(struct osdp_pd *pd, int status)
12711279
evt.notif.arg1 = status;
12721280

12731281
ctx->event_callback(ctx->event_callback_arg, pd->idx, &evt);
1282+
osdp_metrics_report(pd, OSDP_METRIC_EVENT);
12741283
}
12751284

12761285
static int state_update(struct osdp_pd *pd)

0 commit comments

Comments
 (0)