---
 config.c       |   3 +
 ddt.h          |   2 +
 e2e_tc.c       |  36 +++++++
 fd.h           |   1 +
 makefile       |   2 +-
 msg.h          |   2 +-
 notification.h |   3 +
 p2p_tc.c       |  36 +++++++
 pmc.c          | 123 +++++++++++++++++++++-
 pmc_common.c   |  66 +++++++++++-
 port.c         | 277 ++++++++++++++++++++++++++++++++++++++++++++++++-
 port_pm.c      | 212 +++++++++++++++++++++++++++++++++++++
 port_pm.h      | 141 +++++++++++++++++++++++++
 port_private.h |   7 ++
 tlv.c          |  80 ++++++++++++++
 tlv.h          |  35 +++++++
 16 files changed, 1017 insertions(+), 9 deletions(-)
 create mode 100644 port_pm.c
 create mode 100644 port_pm.h

diff --git a/config.c b/config.c
index b104f1b..bc9852d 100644
--- a/config.c
+++ b/config.c
@@ -298,6 +298,9 @@ struct config_item config_tab[] = {
        GLOB_ITEM_INT("offsetScaledLogVariance", 0xffff, 0, UINT16_MAX),
        PORT_ITEM_INT("operLogPdelayReqInterval", 0, INT8_MIN, INT8_MAX),
        PORT_ITEM_INT("operLogSyncInterval", 0, INT8_MIN, INT8_MAX),
+       PORT_ITEM_INT("performance_monitor.15m_history", 0, 0, INT16_MAX),
+       PORT_ITEM_INT("performance_monitor.1h_history", 0, 0, INT16_MAX),
+       PORT_ITEM_INT("performance_monitor.24h_history", 0, 0, INT16_MAX),
        PORT_ITEM_INT("path_trace_enabled", 0, 0, 1),
        PORT_ITEM_INT("phc_index", -1, -1, INT_MAX),
        GLOB_ITEM_DBL("pi_integral_const", 0.0, 0.0, DBL_MAX),
diff --git a/ddt.h b/ddt.h
index 5dc5530..9016857 100644
--- a/ddt.h
+++ b/ddt.h
@@ -26,6 +26,8 @@
 
 typedef Integer64 TimeInterval; /* nanoseconds << 16 */
 
+typedef Integer64 PMTimestamp; /* seconds */
+
 /** On the wire time stamp format. */
 struct Timestamp {
        uint16_t   seconds_msb; /* 16 bits + */
diff --git a/e2e_tc.c b/e2e_tc.c
index 2f8e821..e6d8f3d 100644
--- a/e2e_tc.c
+++ b/e2e_tc.c
@@ -19,6 +19,7 @@
 #include <errno.h>
 
 #include "port.h"
+#include "port_pm.h"
 #include "port_private.h"
 #include "print.h"
 #include "rtnl.h"
@@ -121,6 +122,41 @@ enum fsm_event e2e_event(struct port *p, int fd_index)
                pr_err("unexpected timer expiration");
                return EV_NONE;
 
+       case FD_PM_TIMER:
+               pr_debug("%s: performance monitor timeout", p->log_name);
+               {
+                       struct timespec now;
+                       bool is_pm15m, is_pm1h, is_pm24h;
+
+                       clock_gettime(CLOCK_REALTIME, &now);
+
+                       pm_type(&now, &is_pm15m, &is_pm1h, &is_pm24h);
+
+                       if (is_pm15m) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm15min, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm15min, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM15M_UPDATE);
+                       }
+
+                       if (is_pm1h) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm1h, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm1h, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM1H_UPDATE);
+                       }
+
+                       if (is_pm24h) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm24h, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm24h, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM24H_UPDATE);
+                       }
+
+                       port_tmo_pm(p, port_pm_period(p));
+               }
+               return EV_NONE;
+
        case FD_RTNL:
                pr_debug("%s: received link status notification", p->log_name);
                rtnl_link_status(fd, p->name, port_link_status, p);
diff --git a/fd.h b/fd.h
index 16420d7..df9ccd9 100644
--- a/fd.h
+++ b/fd.h
@@ -40,6 +40,7 @@ enum {
        FD_UNICAST_REQ_TIMER,
        FD_UNICAST_SRV_TIMER,
        FD_RTNL,
+       FD_PM_TIMER,
        N_POLLFD,
 };
 
diff --git a/makefile b/makefile
index 3e3b8b3..16f4924 100644
--- a/makefile
+++ b/makefile
@@ -30,7 +30,7 @@ TS2PHC        = ts2phc.o lstab.o nmea.o serial.o sock.o 
ts2phc_generic_pps_source.o \
  ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o 
ts2phc_pps_source.o
 OBJ    = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \
  e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \
- port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \
+ port.o port_pm.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o 
$(SERVOS) \
  sk.o stats.o tc.o $(TRANSP) telecom.o tlv.o tsproc.o unicast_client.o \
  unicast_fsm.o unicast_service.o util.o version.o
 
diff --git a/msg.h b/msg.h
index 484435d..86fc346 100644
--- a/msg.h
+++ b/msg.h
@@ -178,7 +178,7 @@ struct management_msg {
 } PACKED;
 
 struct message_data {
-       uint8_t buffer[1500];
+       uint8_t buffer[9000];
 } PACKED;
 
 struct ptp_message {
diff --git a/notification.h b/notification.h
index 115f864..20c85e3 100644
--- a/notification.h
+++ b/notification.h
@@ -44,6 +44,9 @@ static inline bool event_bitmask_get(uint8_t *bitmask, 
unsigned int event)
 enum notification {
        NOTIFY_PORT_STATE,
        NOTIFY_TIME_SYNC,
+       NOTIFY_PM15M_UPDATE,
+       NOTIFY_PM1H_UPDATE,
+       NOTIFY_PM24H_UPDATE,
 };
 
 #endif
diff --git a/p2p_tc.c b/p2p_tc.c
index 75cb3b9..3b2ce9c 100644
--- a/p2p_tc.c
+++ b/p2p_tc.c
@@ -19,6 +19,7 @@
 #include <errno.h>
 
 #include "port.h"
+#include "port_pm.h"
 #include "port_private.h"
 #include "print.h"
 #include "rtnl.h"
@@ -124,6 +125,41 @@ enum fsm_event p2p_event(struct port *p, int fd_index)
                pr_err("unexpected timer expiration");
                return EV_NONE;
 
+       case FD_PM_TIMER:
+               pr_debug("%s: performance monitor timeout", p->log_name);
+               {
+                       struct timespec now;
+                       bool is_pm15m, is_pm1h, is_pm24h;
+
+                       clock_gettime(CLOCK_REALTIME, &now);
+
+                       pm_type(&now, &is_pm15m, &is_pm1h, &is_pm24h);
+
+                       if (is_pm15m) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm15min, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm15min, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM15M_UPDATE);
+                       }
+
+                       if (is_pm1h) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm1h, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm1h, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM1H_UPDATE);
+                       }
+
+                       if (is_pm24h) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm24h, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm24h, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM24H_UPDATE);
+                       }
+
+                       port_tmo_pm(p, port_pm_period(p));
+               }
+               return EV_NONE;
+
        case FD_RTNL:
                pr_debug("%s: received link status notification", p->log_name);
                rtnl_link_status(fd, p->name, port_link_status, p);
diff --git a/pmc.c b/pmc.c
index bc87058..9bb9b30 100644
--- a/pmc.c
+++ b/pmc.c
@@ -39,6 +39,7 @@
 static struct pmc *pmc;
 
 #define IFMT "\n\t\t"
+#define IFMT2 "\n\t\t\t"
 #define P41 ((double)(1ULL << 41))
 
 static char *text2str(struct PTPText *text)
@@ -155,6 +156,32 @@ static void pmc_show_signaling(struct ptp_message *msg, 
FILE *fp)
        fflush(fp);
 }
 
+static const char *pmc_show_pm_type(Enumeration16 id)
+{
+       switch (id) {
+       case MID_PM15M_LAST_NP:
+               return "PM15M_LAST_NP";
+       case MID_PM15M_CURRENT_NP:
+               return "PM15M_CURRENT_NP";
+       case MID_PM15M_HISTORY_NP:
+               return "PM15M_HISTORY_NP";
+       case MID_PM1H_LAST_NP:
+               return "PM1H_LAST_NP";
+       case MID_PM1H_CURRENT_NP:
+               return "PM1H_CURRENT_NP";
+       case MID_PM1H_HISTORY_NP:
+               return "PM1H_HISTORY_NP";
+       case MID_PM24H_LAST_NP:
+               return "PM24H_LAST_NP";
+       case MID_PM24H_CURRENT_NP:
+               return "PM24H_CURRENT_NP";
+       case MID_PM24H_HISTORY_NP:
+               return "PM24H_HISTORY_NP";
+       default:
+       }
+       return "";
+}
+
 static void pmc_show(struct ptp_message *msg, FILE *fp)
 {
        struct alternate_time_offset_properties *atop;
@@ -168,9 +195,11 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
        struct unicast_master_entry *ume;
        struct subscribe_events_np *sen;
        struct port_properties_np *ppn;
+       struct pm_history_np *pm_history;
        struct port_hwclock_np *phn;
        struct timePropertiesDS *tp;
        struct management_tlv *mgt;
+       struct pm_conf_np *pm_conf;
        struct time_status_np *tsn;
        struct port_stats_np *pcp;
        struct tlv_extra *extra;
@@ -451,10 +480,16 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
                fprintf(fp, "SUBSCRIBE_EVENTS_NP "
                        IFMT "duration          %hu"
                        IFMT "NOTIFY_PORT_STATE %s"
-                       IFMT "NOTIFY_TIME_SYNC  %s",
+                       IFMT "NOTIFY_TIME_SYNC  %s"
+                       IFMT "NOTIFY_PM15M_UPDATE %s"
+                       IFMT "NOTIFY_PM1H_UPDATE  %s"
+                       IFMT "NOTIFY_PM24H_UPDATE %s",
                        sen->duration,
                        event_bitmask_get(sen->bitmask, NOTIFY_PORT_STATE) ? 
"on" : "off",
-                       event_bitmask_get(sen->bitmask, NOTIFY_TIME_SYNC) ? 
"on" : "off");
+                       event_bitmask_get(sen->bitmask, NOTIFY_TIME_SYNC) ? 
"on" : "off",
+                       event_bitmask_get(sen->bitmask, NOTIFY_PM15M_UPDATE) ? 
"on" : "off",
+                       event_bitmask_get(sen->bitmask, NOTIFY_PM1H_UPDATE) ? 
"on" : "off",
+                       event_bitmask_get(sen->bitmask, NOTIFY_PM24H_UPDATE) ? 
"on" : "off");
                break;
        case MID_SYNCHRONIZATION_UNCERTAIN_NP:
                mtd = (struct management_tlv_datum *) mgt->data;
@@ -651,6 +686,90 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
                fprintf(fp, "LOG_MIN_PDELAY_REQ_INTERVAL "
                        IFMT "logMinPdelayReqInterval %hhd", mtd->val);
                break;
+       case MID_PM_CONF_NP:
+               pm_conf = (struct pm_conf_np *) mgt->data;
+               fprintf(fp, "PM_CONF_NP "
+                       IFMT "15m_history %hhd"
+                       IFMT "1h_history %hhd"
+                       IFMT "24h_history %hhd",
+                       pm_conf->pm15m_history,
+                       pm_conf->pm1h_history,
+                       pm_conf->pm24h_history);
+               break;
+       case MID_PM15M_CURRENT_NP:
+       case MID_PM15M_LAST_NP:
+       case MID_PM15M_HISTORY_NP:
+       case MID_PM1H_CURRENT_NP:
+       case MID_PM1H_LAST_NP:
+       case MID_PM1H_HISTORY_NP:
+       case MID_PM24H_CURRENT_NP:
+       case MID_PM24H_LAST_NP:
+       case MID_PM24H_HISTORY_NP:
+               pm_history = (struct pm_history_np *) mgt->data;
+
+               fprintf(fp, "%s "
+                       IFMT "portIdentity              %s"
+                       IFMT "length                    %hu",
+                       pmc_show_pm_type(mgt->id),
+                       pid2str(&pm_history->portIdentity),
+                       pm_history->length);
+
+               for (i = 0; i < pm_history->length; i++) {
+                       fprintf(fp,
+                               IFMT "Index                     %hu"
+                               IFMT2 "Flags.Valid               %d"
+                               IFMT2 "Flags.Complete            %d"
+                               IFMT2 "Flags.Current             %d"
+                               IFMT2 "Start                     %" PRIu64
+                               IFMT2 "Stop                      %" PRIu64
+                               IFMT2 "rx_Sync                   %" PRIu64
+                               IFMT2 "rx_Delay_Req              %" PRIu64
+                               IFMT2 "rx_Pdelay_Req             %" PRIu64
+                               IFMT2 "rx_Pdelay_Resp            %" PRIu64
+                               IFMT2 "rx_Follow_Up              %" PRIu64
+                               IFMT2 "rx_Delay_Resp             %" PRIu64
+                               IFMT2 "rx_Pdelay_Resp_Follow_Up  %" PRIu64
+                               IFMT2 "rx_Announce               %" PRIu64
+                               IFMT2 "rx_Signaling              %" PRIu64
+                               IFMT2 "rx_Management             %" PRIu64
+                               IFMT2 "tx_Sync                   %" PRIu64
+                               IFMT2 "tx_Delay_Req              %" PRIu64
+                               IFMT2 "tx_Pdelay_Req             %" PRIu64
+                               IFMT2 "tx_Pdelay_Resp            %" PRIu64
+                               IFMT2 "tx_Follow_Up              %" PRIu64
+                               IFMT2 "tx_Delay_Resp             %" PRIu64
+                               IFMT2 "tx_Pdelay_Resp_Follow_Up  %" PRIu64
+                               IFMT2 "tx_Announce               %" PRIu64
+                               IFMT2 "tx_Signaling              %" PRIu64
+                               IFMT2 "tx_Management             %" PRIu64,
+                               pm_history->records[i].index,
+                               pm_history->records[i].flags & 
PM_RECORD_FLAGS_VALID ? 1 : 0,
+                               pm_history->records[i].flags & 
PM_RECORD_FLAGS_COMPLETE ? 1 : 0,
+                               pm_history->records[i].flags & 
PM_RECORD_FLAGS_CURRENT ? 1 : 0,
+                               pm_history->records[i].start,
+                               pm_history->records[i].end,
+                               pm_history->records[i].stats.rxMsgType[SYNC],
+                               
pm_history->records[i].stats.rxMsgType[DELAY_REQ],
+                               
pm_history->records[i].stats.rxMsgType[PDELAY_REQ],
+                               
pm_history->records[i].stats.rxMsgType[PDELAY_RESP],
+                               
pm_history->records[i].stats.rxMsgType[FOLLOW_UP],
+                               
pm_history->records[i].stats.rxMsgType[DELAY_RESP],
+                               
pm_history->records[i].stats.rxMsgType[PDELAY_RESP_FOLLOW_UP],
+                               
pm_history->records[i].stats.rxMsgType[ANNOUNCE],
+                               
pm_history->records[i].stats.rxMsgType[SIGNALING],
+                               
pm_history->records[i].stats.rxMsgType[MANAGEMENT],
+                               pm_history->records[i].stats.txMsgType[SYNC],
+                               
pm_history->records[i].stats.txMsgType[DELAY_REQ],
+                               
pm_history->records[i].stats.txMsgType[PDELAY_REQ],
+                               
pm_history->records[i].stats.txMsgType[PDELAY_RESP],
+                               
pm_history->records[i].stats.txMsgType[FOLLOW_UP],
+                               
pm_history->records[i].stats.txMsgType[DELAY_RESP],
+                               
pm_history->records[i].stats.txMsgType[PDELAY_RESP_FOLLOW_UP],
+                               
pm_history->records[i].stats.txMsgType[ANNOUNCE],
+                               
pm_history->records[i].stats.txMsgType[SIGNALING],
+                               
pm_history->records[i].stats.txMsgType[MANAGEMENT]);
+               }
+               break;
        }
 out:
        fprintf(fp, "\n");
diff --git a/pmc_common.c b/pmc_common.c
index 9e251c4..c02a4d7 100644
--- a/pmc_common.c
+++ b/pmc_common.c
@@ -156,6 +156,16 @@ struct management_id idtab[] = {
        { "UNICAST_MASTER_TABLE_NP", MID_UNICAST_MASTER_TABLE_NP, do_get_action 
},
        { "PORT_HWCLOCK_NP", MID_PORT_HWCLOCK_NP, do_get_action },
        { "POWER_PROFILE_SETTINGS_NP", MID_POWER_PROFILE_SETTINGS_NP, 
do_set_action },
+       { "PM_CONF_NP", MID_PM_CONF_NP, do_set_action },
+       { "PM15M_LAST_NP", MID_PM15M_LAST_NP, do_get_action },
+       { "PM15M_CURRENT_NP", MID_PM15M_CURRENT_NP, do_get_action },
+       { "PM15M_HISTORY_NP", MID_PM15M_HISTORY_NP, do_get_action },
+       { "PM1H_LAST_NP", MID_PM1H_LAST_NP, do_get_action },
+       { "PM1H_CURRENT_NP", MID_PM1H_CURRENT_NP, do_get_action },
+       { "PM1H_HISTORY_NP", MID_PM1H_HISTORY_NP, do_get_action },
+       { "PM24H_LAST_NP", MID_PM24H_LAST_NP, do_get_action },
+       { "PM24H_CURRENT_NP", MID_PM24H_CURRENT_NP, do_get_action },
+       { "PM24H_HISTORY_NP", MID_PM24H_HISTORY_NP, do_get_action },
 };
 
 static void do_get_action(struct pmc *pmc, int action, int index, char *str)
@@ -175,9 +185,13 @@ static void do_set_action(struct pmc *pmc, int action, int 
index, char *str)
        struct grandmaster_settings_np gsn;
        struct management_tlv_datum mtd;
        struct subscribe_events_np sen;
+       struct pm_conf_np pm_conf;
        struct port_ds_np pnp;
        char onoff_port_state[4] = "off";
        char onoff_time_status[4] = "off";
+       char onoff_pm15m_update[4] = "off";
+       char onoff_pm1h_update[4] = "off";
+       char onoff_pm24h_update[4] = "off";
        char display_name[11] = {0};
        uint64_t jump;
        uint8_t key;
@@ -303,12 +317,18 @@ static void do_set_action(struct pmc *pmc, int action, 
int index, char *str)
                cnt = sscanf(str, " %*s %*s "
                             "duration          %hu "
                             "NOTIFY_PORT_STATE %3s "
-                            "NOTIFY_TIME_SYNC  %3s ",
+                            "NOTIFY_TIME_SYNC  %3s "
+                            "NOTIFY_PM15M_UPDATE %3s "
+                            "NOTIFY_PM1H_UPDATE %3s "
+                            "NOTIFY_PM24H_UPDATE %3s ",
                             &sen.duration,
                             onoff_port_state,
-                            onoff_time_status);
-               if (cnt != 3) {
-                       fprintf(stderr, "%s SET needs 3 values\n",
+                            onoff_time_status,
+                            onoff_pm15m_update,
+                            onoff_pm1h_update,
+                            onoff_pm24h_update);
+               if (cnt < 3) {
+                       fprintf(stderr, "%s SET needs 3 values at least\n",
                                idtab[index].name);
                        break;
                }
@@ -318,6 +338,15 @@ static void do_set_action(struct pmc *pmc, int action, int 
index, char *str)
                if (!strcasecmp(onoff_time_status, "on")) {
                        event_bitmask_set(sen.bitmask, NOTIFY_TIME_SYNC, TRUE);
                }
+               if (!strcasecmp(onoff_pm15m_update, "on")) {
+                       event_bitmask_set(sen.bitmask, NOTIFY_PM15M_UPDATE, 
TRUE);
+               }
+               if (!strcasecmp(onoff_pm1h_update, "on")) {
+                       event_bitmask_set(sen.bitmask, NOTIFY_PM1H_UPDATE, 
TRUE);
+               }
+               if (!strcasecmp(onoff_pm24h_update, "on")) {
+                       event_bitmask_set(sen.bitmask, NOTIFY_PM24H_UPDATE, 
TRUE);
+               }
                pmc_send_set_action(pmc, code, &sen, sizeof(sen));
                break;
        case MID_SYNCHRONIZATION_UNCERTAIN_NP:
@@ -385,6 +414,21 @@ static void do_set_action(struct pmc *pmc, int action, int 
index, char *str)
                                IEEE_C37_238_VERSION_2017);
                }
                break;
+       case MID_PM_CONF_NP:
+               cnt = sscanf(str, " %*s %*s "
+                            "15m_history %hu "
+                            "1h_history  %hu "
+                            "24h_history %hu ",
+                            &pm_conf.pm15m_history,
+                            &pm_conf.pm1h_history,
+                                &pm_conf.pm24h_history);
+               if (cnt != 3) {
+                       fprintf(stderr, "%s SET needs 3 values\n",
+                               idtab[index].name);
+                       break;
+               }
+               pmc_send_set_action(pmc, code, &pm_conf, sizeof(pm_conf));
+               break;
        }
 }
 
@@ -675,6 +719,20 @@ static int pmc_tlv_datalen(struct pmc *pmc, int id)
        case MID_LOG_MIN_PDELAY_REQ_INTERVAL:
                len += sizeof(struct management_tlv_datum);
                break;
+       case MID_PM_CONF_NP:
+               len += sizeof(struct pm_conf_np);
+               break;
+       case MID_PM15M_LAST_NP:
+       case MID_PM15M_CURRENT_NP:
+       case MID_PM15M_HISTORY_NP:
+       case MID_PM1H_LAST_NP:
+       case MID_PM1H_CURRENT_NP:
+       case MID_PM1H_HISTORY_NP:
+       case MID_PM24H_LAST_NP:
+       case MID_PM24H_CURRENT_NP:
+       case MID_PM24H_HISTORY_NP:
+               len += sizeof(struct pm_history_np);
+               break;
        }
        return len + len % 2;
 }
diff --git a/port.c b/port.c
index 5803cd3..86b7e98 100644
--- a/port.c
+++ b/port.c
@@ -21,8 +21,10 @@
 #include <malloc.h>
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 #include <unistd.h>
 #include <sys/queue.h>
+#include <sys/timerfd.h>
 #include <net/if.h>
 
 #include "bmc.h"
@@ -48,6 +50,10 @@
 #define ALLOWED_LOST_RESPONSES 3
 #define ANNOUNCE_SPAN 1
 
+#define PM_15M   15
+#define PM_1H    60
+#define PM_24H   1440
+
 enum syfu_event {
        SYNC_MISMATCH,
        SYNC_MATCH,
@@ -258,6 +264,18 @@ int set_tmo_log(int fd, unsigned int scale, int 
log_seconds)
        return timerfd_settime(fd, 0, &tmo, NULL);
 }
 
+int set_tmo_peridic(int fd, int seconds)
+{
+       struct itimerspec tmo = {
+               {0, 0}, {0, 0}
+       };
+
+       tmo.it_interval.tv_sec = seconds;
+       tmo.it_value.tv_sec = seconds;
+
+       return timerfd_settime(fd, 0, &tmo, NULL);
+}
+
 int set_tmo_lin(int fd, int seconds)
 {
        struct itimerspec tmo = {
@@ -291,6 +309,33 @@ int set_tmo_random(int fd, int min, int span, int 
log_seconds)
        return timerfd_settime(fd, 0, &tmo, NULL);
 }
 
+int port_tmo_pm(struct port *p, unsigned int period_min)
+{
+       struct itimerspec tmo = {
+               {0, 0}, {0, 0}
+       };
+
+       struct timespec now;
+       struct tm tm;
+       time_t time;
+
+       if (period_min) {
+               clock_gettime(CLOCK_REALTIME, &now);
+               time = now.tv_sec;
+               localtime_r(&time, &tm);
+
+               tm.tm_sec = 0;
+               tm.tm_min = (tm.tm_min / period_min) * period_min + period_min;
+
+               time = mktime(&tm);
+               tmo.it_value.tv_sec = time;
+
+               return timerfd_settime(p->fda.fd[FD_PM_TIMER], 
TFD_TIMER_ABSTIME, &tmo, NULL);
+       } else {
+               return port_clr_tmo(p->fda.fd[FD_PM_TIMER]);
+       }
+}
+
 int port_set_fault_timer_log(struct port *port,
                             unsigned int scale, int log_seconds)
 {
@@ -338,6 +383,73 @@ static void fc_prune(struct foreign_clock *fc)
        }
 }
 
+
+static void port_init_pm(struct port *p)
+{
+       p->pm15min = NULL;
+       p->pm1h = NULL;
+       p->pm24h = NULL;
+
+       p->fda.fd[FD_PM_TIMER] = timerfd_create(CLOCK_REALTIME, 0);
+}
+
+unsigned int port_pm_period(struct port *p)
+{
+       if (port_pm_size(p->pm15min) > 0) {
+               return PM_15M;
+       } else if (port_pm_size(p->pm1h) > 0) {
+               return PM_1H;
+       } else if (port_pm_size(p->pm24h) > 0) {
+               return PM_24H;
+       }
+       return 0;
+}
+
+static void port_enable_pm(struct port *p, unsigned int pm15min_hist, unsigned 
int pm1h_hist, unsigned int pm24h_hist)
+{
+       struct timespec now;
+       unsigned int size;
+
+       clock_gettime(CLOCK_REALTIME, &now);
+
+       size = port_pm_size(p->pm15min);
+       p->pm15min = port_pm_resize(p->pm15min, pm15min_hist, PM_15M);
+       if (!size && pm15min_hist) {
+               port_pm_prepare_sample(p->pm15min, now.tv_sec);
+               port_pm_checkpoint(p->pm15min, &p->stats);
+       }
+
+       size = port_pm_size(p->pm1h);
+       p->pm1h = port_pm_resize(p->pm1h, pm1h_hist, PM_1H);
+       if (!size && pm1h_hist) {
+               port_pm_prepare_sample(p->pm1h, now.tv_sec);
+               port_pm_checkpoint(p->pm1h, &p->stats);
+       }
+
+       size = port_pm_size(p->pm24h);
+       p->pm24h = port_pm_resize(p->pm24h, pm24h_hist, PM_24H);
+       if (!size && pm24h_hist) {
+               port_pm_prepare_sample(p->pm24h, now.tv_sec);
+               port_pm_checkpoint(p->pm24h, &p->stats);
+       }
+
+       port_tmo_pm(p, port_pm_period(p));
+}
+
+static void port_destroy_pm(struct port *p)
+{
+       if (p->fda.fd[FD_PM_TIMER] >= 0) {
+               close(p->fda.fd[FD_PM_TIMER]);
+               p->fda.fd[FD_PM_TIMER] = -1;
+       }
+       port_pm_destroy(p->pm15min);
+       port_pm_destroy(p->pm1h);
+       port_pm_destroy(p->pm24h);
+       p->pm15min = NULL;
+       p->pm1h = NULL;
+       p->pm24h = NULL;
+}
+
 static int delay_req_current(struct ptp_message *m, struct timespec now)
 {
        int64_t t1, t2, tmo = 5 * NSEC2SEC;
@@ -874,6 +986,9 @@ static const Octet profile_id_p2p[] = {0x00, 0x1B, 0x19, 
0x00, 0x02, 0x00};
 static const Octet profile_id_8275_1[] = {0x00, 0x19, 0xA7, 0x01, 0x02, 0x03};
 static const Octet profile_id_8275_2[] = {0x00, 0x19, 0xA7, 0x02, 0x01, 0x02};
 
+#define __min(a, b) ((a) < (b) ? (a) : (b))
+
+
 static int port_management_fill_response(struct port *target,
                                         struct ptp_message *rsp, int id)
 {
@@ -884,17 +999,22 @@ static int port_management_fill_response(struct port 
*target,
        struct mgmt_clock_description *cd;
        struct management_tlv_datum *mtd;
        struct unicast_master_entry *ume;
+       struct pm_history_np *pm_history;
        struct clock_description *desc;
        struct port_properties_np *ppn;
        struct port_hwclock_np *phn;
+       struct pm_conf_np *pm_conf;
        struct management_tlv *tlv;
        struct port_stats_np *psn;
        struct foreign_clock *fc;
        struct port_ds_np *pdsnp;
        struct tlv_extra *extra;
        struct PortIdentity pid;
+       struct port_pm *port_pm;
        const char *ts_label;
+       struct timespec now;
        struct portDS *pds;
+       unsigned int i;
        uint16_t u16;
        uint8_t *buf;
        int datalen;
@@ -1129,6 +1249,82 @@ static int port_management_fill_response(struct port 
*target,
                memcpy(pwr, &target->pwr, sizeof(*pwr));
                datalen = sizeof(*pwr);
                break;
+       case MID_PM_CONF_NP:
+               pm_conf = (struct pm_conf_np *)tlv->data;
+               pm_conf->pm15m_history = port_pm_size(target->pm15min);
+               pm_conf->pm1h_history = port_pm_size(target->pm1h);
+               pm_conf->pm24h_history = port_pm_size(target->pm24h);
+               pm_conf->dummy = 0;
+               datalen = sizeof(*pm_conf);
+               break;
+       case MID_PM15M_LAST_NP:
+       case MID_PM15M_CURRENT_NP:
+       case MID_PM15M_HISTORY_NP:
+       case MID_PM1H_LAST_NP:
+       case MID_PM1H_CURRENT_NP:
+       case MID_PM1H_HISTORY_NP:
+       case MID_PM24H_LAST_NP:
+       case MID_PM24H_CURRENT_NP:
+       case MID_PM24H_HISTORY_NP:
+               clock_gettime(CLOCK_REALTIME, &now);
+               pm_history = (struct pm_history_np *)tlv->data;
+               switch (id) {
+               case MID_PM15M_LAST_NP:
+                       port_pm = target->pm15min;
+                       u16 = port_pm_last(port_pm);
+                       pm_history->length = __min(1, 
port_pm_fill_level(port_pm));
+                       break;
+               case MID_PM15M_CURRENT_NP:
+                       port_pm = target->pm15min;
+                       u16 = port_pm_current(port_pm);
+                       pm_history->length = 1;
+                       break;
+               case MID_PM15M_HISTORY_NP:
+                       port_pm = target->pm15min;
+                       u16 = port_pm_current(port_pm);
+                       pm_history->length = port_pm_fill_level(port_pm) + 1;
+                       break;
+               case MID_PM1H_LAST_NP:
+                       port_pm = target->pm1h;
+                       u16 = port_pm_last(port_pm);
+                       pm_history->length = __min(1, 
port_pm_fill_level(port_pm));
+                       break;
+               case MID_PM1H_CURRENT_NP:
+                       port_pm = target->pm1h;
+                       u16 = port_pm_current(port_pm);
+                       pm_history->length = 1;
+                       break;
+               case MID_PM1H_HISTORY_NP:
+                       port_pm = target->pm1h;
+                       u16 = port_pm_current(port_pm);
+                       pm_history->length = port_pm_fill_level(port_pm) + 1;
+                       break;
+               case MID_PM24H_LAST_NP:
+                       port_pm = target->pm24h;
+                       u16 = port_pm_last(port_pm);
+                       pm_history->length = __min(1, 
port_pm_fill_level(port_pm));
+                       break;
+               case MID_PM24H_CURRENT_NP:
+                       port_pm = target->pm24h;
+                       u16 = port_pm_current(port_pm);
+                       pm_history->length = 1;
+                       break;
+               case MID_PM24H_HISTORY_NP:
+                       port_pm = target->pm24h;
+                       u16 = port_pm_current(port_pm);
+                       pm_history->length = port_pm_fill_level(port_pm) + 1;
+                       break;
+               }
+               for (i = u16 - pm_history->length + 1; i <= u16; i++) {
+                       if (i == port_pm_current(port_pm)) {
+                               port_pm_get_current_sample(port_pm, now.tv_sec, 
&target->stats, &pm_history->records[u16 - i]);
+                       } else {
+                               port_pm_get_sample(port_pm, i, 
&pm_history->records[u16 - i]);
+                       }
+               }
+               pm_history->portIdentity = target->portIdentity;
+               datalen = sizeof(struct pm_history_np) + (sizeof(struct 
pm_record_np) * pm_history->length);
+               break;
        default:
                /* The caller should *not* respond to this message. */
                tlv_extra_recycle(extra);
@@ -1172,6 +1368,7 @@ static int port_management_set(struct port *target,
 {
        struct ieee_c37_238_settings_np *pwr;
        struct management_tlv *tlv;
+       struct pm_conf_np *pm_conf;
        struct port_ds_np *pdsnp;
        int respond = 0;
 
@@ -1194,6 +1391,15 @@ static int port_management_set(struct port *target,
                        break;
                }
                break;
+       case MID_PM_CONF_NP:
+               pm_conf = (struct pm_conf_np *) tlv->data;
+
+               config_set_section_int(clock_config(target->clock), 
target->name, "performance_monitor.15m_history", pm_conf->pm15m_history);
+               config_set_section_int(clock_config(target->clock), 
target->name, "performance_monitor.1h_history", pm_conf->pm1h_history);
+               config_set_section_int(clock_config(target->clock), 
target->name, "performance_monitor.24h_history", pm_conf->pm24h_history);
+               port_enable_pm(target, pm_conf->pm15m_history, 
pm_conf->pm1h_history, pm_conf->pm24h_history);
+               respond = 1;
+               break;
        }
        if (respond && !port_management_get_response(target, ingress, id, req))
                pr_err("%s: failed to send management set response", 
target->log_name);
@@ -2593,6 +2799,7 @@ void port_close(struct port *p)
        unicast_service_cleanup(p);
        transport_destroy(p->trp);
        tsproc_destroy(p->tsproc);
+       port_destroy_pm(p);
        if (p->fault_fd >= 0) {
                close(p->fault_fd);
        }
@@ -2841,6 +3048,22 @@ enum fsm_event port_event(struct port *p, int fd_index)
        return p->event(p, fd_index);
 }
 
+void pm_type(struct timespec *now, bool *is_15m, bool *is_1h, bool *is_24h)
+{
+       struct tm tm;
+       time_t time;
+       int tm_min;
+
+       time = now->tv_sec;
+       localtime_r(&time, &tm);
+
+       tm_min = tm.tm_hour * 60 + tm.tm_min;
+
+       *is_15m = (tm_min % PM_15M) <= 1 || (tm_min % PM_15M) >= (PM_15M - 1);
+       *is_1h = (tm_min % PM_1H) <= 1 || (tm_min % PM_1H) >= (PM_1H - 1);
+       *is_24h = (tm_min % PM_24H) <= 1 || (tm_min % PM_24H) >= (PM_24H - 1);
+}
+
 static enum fsm_event bc_event(struct port *p, int fd_index)
 {
        enum fsm_event event = EV_NONE;
@@ -2937,6 +3160,41 @@ static enum fsm_event bc_event(struct port *p, int 
fd_index)
                p->service_stats.unicast_request_timeout++;
                return unicast_client_timer(p) ? EV_FAULT_DETECTED : EV_NONE;
 
+       case FD_PM_TIMER:
+               pr_debug("%s: performance monitor timeout", p->log_name);
+               {
+                       struct timespec now;
+                       bool is_pm15m, is_pm1h, is_pm24h;
+
+                       clock_gettime(CLOCK_REALTIME, &now);
+
+                       pm_type(&now, &is_pm15m, &is_pm1h, &is_pm24h);
+
+                       if (is_pm15m) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm15min, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm15min, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM15M_UPDATE);
+                       }
+
+                       if (is_pm1h) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm1h, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm1h, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM1H_UPDATE);
+                       }
+
+                       if (is_pm24h) {
+                               // 15min pm expired
+                               port_pm_store_sample(p->pm24h, now.tv_sec, 
&p->stats);
+                               port_pm_prepare_sample(p->pm24h, now.tv_sec);
+                               port_notify_event(p, NOTIFY_PM24H_UPDATE);
+                       }
+
+                       port_tmo_pm(p, port_pm_period(p));
+               }
+               return EV_NONE;
+
        case FD_RTNL:
                pr_debug("%s: received link status notification", p->log_name);
                rtnl_link_status(fd, p->name, port_link_status, p);
@@ -3258,6 +3516,15 @@ void port_notify_event(struct port *p, enum notification 
event)
        case NOTIFY_PORT_STATE:
                id = MID_PORT_DATA_SET;
                break;
+       case NOTIFY_PM15M_UPDATE:
+               id = MID_PM15M_LAST_NP;
+               break;
+       case NOTIFY_PM1H_UPDATE:
+               id = MID_PM1H_LAST_NP;
+               break;
+       case NOTIFY_PM24H_UPDATE:
+               id = MID_PM24H_LAST_NP;
+               break;
        default:
                return;
        }
@@ -3285,6 +3552,7 @@ struct port *port_open(const char *phc_device,
        enum clock_type type = clock_type(clock);
        struct config *cfg = clock_config(clock);
        struct port *p = malloc(sizeof(*p));
+       int pm15m_history, pm1h_history, pm24h_history;
        int i;
 
        if (!p) {
@@ -3456,7 +3724,7 @@ struct port *port_open(const char *phc_device,
        }
        p->nrate.ratio = 1.0;
 
-       port_clear_fda(p, N_POLLFD);
+       port_clear_fda(p, FD_PM_TIMER);
        p->fault_fd = -1;
        if (!port_is_uds(p)) {
                p->fault_fd = timerfd_create(CLOCK_MONOTONIC, 0);
@@ -3465,6 +3733,13 @@ struct port *port_open(const char *phc_device,
                        goto err_tsproc;
                }
        }
+
+       port_init_pm(p);
+       pm15m_history = config_get_int(cfg, p->name, 
"performance_monitor.15m_history");
+       pm1h_history = config_get_int(cfg, p->name, 
"performance_monitor.1h_history");
+       pm24h_history = config_get_int(cfg, p->name, 
"performance_monitor.24h_history");
+       port_enable_pm(p, pm15m_history, pm1h_history, pm24h_history);
+
        return p;
 
 err_tsproc:
diff --git a/port_pm.c b/port_pm.c
new file mode 100644
index 0000000..b6b007b
--- /dev/null
+++ b/port_pm.c
@@ -0,0 +1,212 @@
+/**
+ * @file port_pm.c
+ * @brief Port Level Performance Monitor
+ * @note Copyright (C) 2023 Luigi Mantellini <luigi.mantell...@sm-optics.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "port_pm.h"
+#include "ddt.h"
+#include "pdt.h"
+
+struct port_pm {
+       struct PortStats stats;
+       unsigned int period_min;
+       UInteger16 history_size;
+       UInteger16 fill_level;
+       UInteger16 index;
+       PMTimestamp start;
+       struct pm_record_np *entries;
+};
+
+struct port_pm* port_pm_init(unsigned int history_size, unsigned int 
period_min)
+{
+       struct port_pm *port_pm;
+
+       port_pm = malloc(sizeof(struct port_pm));
+       if (!port_pm) {
+               goto fail_port_pm;
+       }
+
+       port_pm->entries = calloc(history_size, sizeof(struct pm_record_np));
+       if (!port_pm->entries) {
+               goto fail_entries;
+       }
+
+       port_pm->period_min = period_min;
+       port_pm->history_size = history_size;
+       port_pm->fill_level = 0;
+       port_pm->index = 0;
+       port_pm->start = 0;
+
+       return port_pm;
+
+fail_entries:
+       free(port_pm);
+       port_pm = NULL;
+fail_port_pm:
+       return port_pm;
+}
+
+void port_pm_destroy(struct port_pm *port_pm)
+{
+       if (port_pm) {
+               if (port_pm->entries) {
+                       free(port_pm->entries);
+               }
+               free(port_pm);
+               port_pm = NULL;
+       }
+}
+
+#define __min(a, b) (a < b ? a : b)
+
+struct port_pm* port_pm_resize(struct port_pm *port_pm, unsigned int 
history_size, unsigned int period_min)
+{
+       if (!history_size) {
+               port_pm_destroy(port_pm);
+               return port_pm;
+       }
+
+       if (!port_pm) {
+               return port_pm_init(history_size, period_min);
+       }
+
+       if (history_size != port_pm->history_size) {
+               struct pm_record_np *entries = calloc(history_size, 
sizeof(struct pm_record_np));
+               unsigned int n = __min(port_pm->history_size, history_size);
+               unsigned int first = (port_pm->index > n) ? (port_pm->index - 
n) : 0;
+               int i;
+
+               for (i = first; i < first + n; i++) {
+                       entries[i % history_size] = port_pm->entries[i % 
port_pm->history_size];
+               }
+               free(port_pm->entries);
+               port_pm->entries = entries;
+               port_pm->history_size = history_size;
+               port_pm->fill_level = __min(port_pm->fill_level, history_size);
+       }
+
+       return port_pm;
+}
+
+UInteger16 port_pm_size(const struct port_pm *port_pm)
+{
+       if (port_pm) {
+               return port_pm->history_size;
+       }
+       return 0;
+}
+
+UInteger16 port_pm_fill_level(const struct port_pm *port_pm)
+{
+       if (port_pm) {
+               return port_pm->fill_level;
+       }
+       return 0;
+}
+
+int port_pm_has_sample(const struct port_pm *port_pm)
+{
+       if (port_pm) {
+               return port_pm->fill_level > 0;
+       }
+       return 0;
+}
+
+UInteger16 port_pm_last(const struct port_pm *port_pm)
+{
+       if (port_pm) {
+               return port_pm->index - 1;
+       }
+       return 0;
+}
+
+UInteger16 port_pm_current(const struct port_pm *port_pm)
+{
+       if (port_pm) {
+               return port_pm->index;
+       }
+       return 0;
+}
+
+void port_pm_prepare_sample(struct port_pm *port_pm, PMTimestamp timestamp)
+{
+       if (port_pm) {
+               port_pm->start = timestamp;
+       }
+}
+
+void port_pm_checkpoint(struct port_pm *port_pm, const struct PortStats *stats)
+{
+       if (port_pm && stats) {
+               port_pm->stats = *stats;
+       }
+}
+
+void port_pm_get_current_sample(struct port_pm *port_pm, PMTimestamp 
timestamp, const struct PortStats *stats, struct pm_record_np *sample)
+{
+       if (port_pm && sample) {
+               if (port_pm->history_size) {
+                       PMTimestamp delta;
+
+                       for (unsigned int i = 0; i < MAX_MESSAGE_TYPES; i++) {
+                               sample->stats.rxMsgType[i] = 
stats->rxMsgType[i] - port_pm->stats.rxMsgType[i];
+                               sample->stats.txMsgType[i] = 
stats->txMsgType[i] - port_pm->stats.txMsgType[i];
+                       }
+
+                       sample->start = port_pm->start;
+                       sample->end = timestamp;
+                       delta = sample->end - sample->start;
+                       sample->flags = PM_RECORD_FLAGS_VALID | 
PM_RECORD_FLAGS_CURRENT | (delta >= (port_pm->period_min * 60 * 2 / 3) ? 
PM_RECORD_FLAGS_COMPLETE : 0);
+                       sample->index = port_pm->index;
+               } else {
+                       memset(sample, 0, sizeof(struct pm_record_np));
+               }
+       }
+}
+
+int port_pm_get_sample(struct port_pm *port_pm, UInteger16 index, struct 
pm_record_np *sample)
+{
+       if (port_pm && sample) {
+               if ((index < port_pm->index) && (index >= (port_pm->index - 
port_pm->history_size))) {
+                       memcpy(sample, &port_pm->entries[index % 
port_pm->history_size], sizeof(struct pm_record_np));
+                       return 1;
+               } else {
+                       memset(sample, 0, sizeof(struct pm_record_np));
+               }
+       }
+       return 0;
+}
+
+void port_pm_store_sample(struct port_pm *port_pm, PMTimestamp timestamp, 
const struct PortStats *stats)
+{
+       struct pm_record_np *entry;
+       if (port_pm && stats) {
+               entry = &port_pm->entries[port_pm->index % 
port_pm->history_size];
+               port_pm_get_current_sample(port_pm, timestamp, stats, entry);
+               entry->flags &= ~PM_RECORD_FLAGS_CURRENT;
+               port_pm_checkpoint(port_pm, stats);
+
+               port_pm->index++;
+               if (port_pm->fill_level < port_pm->history_size) {
+                       port_pm->fill_level++;
+               }
+       }
+}
diff --git a/port_pm.h b/port_pm.h
new file mode 100644
index 0000000..01be6e6
--- /dev/null
+++ b/port_pm.h
@@ -0,0 +1,141 @@
+/**
+ * @file port_pm.h
+ * @brief Port Level Performance Monitoring Circular Buffer
+ * @note Copyright (C) 2023 Luigi Mantellini <luigi.mantell...@sm-optics.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef HAVE_PORT_PM_H
+#define HAVE_PORT_PM_H
+
+#include "ddt.h"
+#include "tlv.h"
+
+/** Opaque type. */
+struct port_pm;
+
+/**
+ * Create a new Port Performance Monitoring circular buffer.
+ *
+ * @param history_size Performance Monitoring circular buffer size.
+ * @param period_min   Sampling period in minutes used to handle COMPLETE flag 
(See 1588-2019 Annex J)
+ * @return The created object
+ */
+struct port_pm* port_pm_init(unsigned int history_size, unsigned int 
period_min);
+
+/**
+ * Destroy a Port Performance Montitoring buffer and free its associated 
resources.
+ * After this call returns, @a port_pm is no longer a valid pointer.
+ *
+ * @param port_pm A pointer to an already allocated Port Performance 
Monitoring circular buffer.
+ */
+void port_pm_destroy(struct port_pm *port_pm);
+
+/**
+ * Resize a Port Performance Monitoring circular buffer. If the passed buffer 
is not created yet it will
+ * created calling port_pm_init(). Instead a zero history_size call will 
destroy the buffer calling the
+ * port_pm_destroy().
+ *
+ * @param port_pm      The Port Performance Monitoring circular buffer.
+ * @param history_size Performance Monitoring circular buffer size.
+ * @param period_min   Sampling period in minutes used to handle COMPLETE flag 
(See 1588-2019 Annex J)
+ * @return The created object or NULL if destroyed.
+ */
+struct port_pm* port_pm_resize(struct port_pm *port_pm, unsigned int 
history_size, unsigned int period_min);
+
+/**
+ * Returns the circular buffer size.
+ *
+ * @param port_pm The pointer to a Port Performance Monitoring circular buffer.
+ */
+UInteger16 port_pm_size(const struct port_pm *port_pm);
+
+/**
+ * Returns the filling level of a circular buffer.
+ *
+ * @param port_pm The pointer to a Port Performance Monitoring circular buffer.
+ * @return        The filling level of the circular buffer.
+ */
+UInteger16 port_pm_fill_level(const struct port_pm *port_pm);
+
+/**
+ * Returns true when the buffer has at least a sample.
+ *
+ * @param port_pm The pointer to a Port Performance Monitoring circular buffer.
+ * @return        True when the buffer has at least a sample, false otherwise.
+ */
+int port_pm_has_sample(const struct port_pm *port_pm);
+
+/**
+ * Return the index of the last sample.
+ *
+ * @param port_pm The pointer to a Port Performance Monitoring circular buffer.
+ * @return        The index of the last stored sample.
+ */
+UInteger16 port_pm_last(const struct port_pm *port_pm);
+
+/**
+ * Return the index of the current (and not finished) sample.
+ *
+ * @param port_pm The pointer to a Port Performance Monitoring circular buffer.
+ * @param         The index of the current sample.
+ */
+UInteger16 port_pm_current(const struct port_pm *port_pm);
+
+/**
+ * Store information required for the next sample.
+ *
+ * @param port_pm The pointer to a Port Performance Monitoring circular buffer.
+ */
+void port_pm_prepare_sample(struct port_pm *port_pm, PMTimestamp timestamp);
+
+/**
+ * Store the current port counters (Stats) used for delta computaion.
+ *
+ * @param port_pm The pointer to a Port Performance Monitoring circular buffer.
+ */
+void port_pm_checkpoint(struct port_pm *port_pm, const struct PortStats 
*stats);
+
+/**
+ * Get the current (not stored) sample. The caller must provide the timestamp 
(in seconds) and
+ * the current Port Counters.
+ *
+ * @param port_pm   The pointer to a Port Performance Monitoring circular 
buffer.
+ * @param timestamp The current timestamp in seconds.
+ * @param stats     The current Port Counters.
+ * @param sample    The pointer to sample to fill
+ */
+void port_pm_get_current_sample(struct port_pm *port_pm, PMTimestamp 
timestamp, const struct PortStats *stats, struct pm_record_np *sample);
+
+/**
+ * Get the stored sample at provied index
+ *
+ * @param port_pm   The pointer to a Port Performance Monitoring circular 
buffer.
+ * @param index     Index of the sample into circular buffer.
+ * @param sample    The pointer to sample to fill
+ * @return          Return 1 on succes, 0 otherwise.
+ */
+int port_pm_get_sample(struct port_pm *port_pm, UInteger16 index, struct 
pm_record_np *sample);
+
+/**
+ * Store the sample updating the timestamp and the actual stored Port couters.
+ *
+ * @param port_pm    The pointer to a Port Performance Monitoring circular 
buffer.
+ * @param timestamp  The current timestamp in seconds.
+ * @param stats      The current Port Counters.
+ */
+void port_pm_store_sample(struct port_pm *port_pm, PMTimestamp timestamp, 
const struct PortStats *stats);
+
+#endif
diff --git a/port_private.h b/port_private.h
index 3b02d2f..9352bd6 100644
--- a/port_private.h
+++ b/port_private.h
@@ -27,6 +27,7 @@
 #include "monitor.h"
 #include "msg.h"
 #include "power_profile.h"
+#include "port_pm.h"
 #include "tmv.h"
 
 #define NSEC2SEC 1000000000LL
@@ -164,6 +165,9 @@ struct port {
        /* slave event monitoring */
        struct monitor *slave_event_monitor;
        bool unicast_state_dirty;
+       struct port_pm *pm15min;
+       struct port_pm *pm1h;
+       struct port_pm *pm24h;
 };
 
 #define portnum(p) (p->portIdentity.portNumber)
@@ -211,5 +215,8 @@ int process_signaling(struct port *p, struct ptp_message 
*m);
 void process_sync(struct port *p, struct ptp_message *m);
 int source_pid_eq(struct ptp_message *m1, struct ptp_message *m2);
 void ts_add(tmv_t *ts, Integer64 correction);
+void pm_type(struct timespec *now, bool *is_15m, bool *is_1h, bool *is_24h);
+int port_tmo_pm(struct port *p, unsigned int period_min);
+unsigned int port_pm_period(struct port *p);
 
 #endif
diff --git a/tlv.c b/tlv.c
index 9b82bd9..af9d3d3 100644
--- a/tlv.c
+++ b/tlv.c
@@ -112,6 +112,32 @@ static int64_t net2host64_unaligned(void *p)
        return v;
 }
 
+static void host2net_pm_entry(struct pm_record_np *entry)
+{
+       int i;
+       for (i = 0 ; i < MAX_MESSAGE_TYPES; i++) {
+               entry->stats.rxMsgType[i] = 
__cpu_to_le64(entry->stats.rxMsgType[i]);
+               entry->stats.txMsgType[i] = 
__cpu_to_le64(entry->stats.txMsgType[i]);
+       }
+       HTONS(entry->index);
+       HTONS(entry->flags);
+       entry->start = host2net64(entry->start);
+       entry->end = host2net64(entry->end);
+}
+
+static void net2host_pm_entry(struct pm_record_np *entry)
+{
+       int i;
+       for (i = 0 ; i < MAX_MESSAGE_TYPES; i++) {
+               entry->stats.rxMsgType[i] = 
__le64_to_cpu(entry->stats.rxMsgType[i]);
+               entry->stats.txMsgType[i] = 
__le64_to_cpu(entry->stats.txMsgType[i]);
+       }
+       NTOHS(entry->index);
+       NTOHS(entry->flags);
+       entry->start =net2host64(entry->start);
+       entry->end = net2host64(entry->end);
+}
+
 static size_t tlv_array_count(struct TLV *tlv, size_t base_size, size_t 
item_size)
 {
        return (tlv->length - base_size) / item_size;
@@ -173,9 +199,11 @@ static int mgt_post_recv(struct management_tlv *m, 
uint16_t data_len,
        struct mgmt_clock_description *cd;
        struct unicast_master_entry *ume;
        struct subscribe_events_np *sen;
+       struct pm_history_np *pm_history;
        struct port_properties_np *ppn;
        struct port_hwclock_np *phn;
        struct timePropertiesDS *tp;
+       struct pm_conf_np *pm_conf;
        struct time_status_np *tsn;
        struct port_stats_np *psn;
        int extra_len = 0, i, len;
@@ -490,6 +518,33 @@ static int mgt_post_recv(struct management_tlv *m, 
uint16_t data_len,
                if (data_len != 0)
                        goto bad_length;
                break;
+       case MID_PM_CONF_NP:
+               if (data_len < sizeof(struct pm_conf_np))
+                       goto bad_length;
+               pm_conf = (struct pm_conf_np *)m->data;
+               NTOHS(pm_conf->pm15m_history);
+               NTOHS(pm_conf->pm1h_history);
+               NTOHS(pm_conf->pm24h_history);
+               break;
+       case MID_PM15M_LAST_NP:
+       case MID_PM15M_CURRENT_NP:
+       case MID_PM15M_HISTORY_NP:
+       case MID_PM1H_LAST_NP:
+       case MID_PM1H_CURRENT_NP:
+       case MID_PM1H_HISTORY_NP:
+       case MID_PM24H_LAST_NP:
+       case MID_PM24H_CURRENT_NP:
+       case MID_PM24H_HISTORY_NP:
+               if (data_len < sizeof(struct pm_history_np))
+                       goto bad_length;
+               pm_history = (struct pm_history_np *)m->data;
+               pm_history->portIdentity.portNumber =
+                       ntohs(pm_history->portIdentity.portNumber);
+               NTOHS(pm_history->length);
+               for (i = 0; i < pm_history->length; i++) {
+                       net2host_pm_entry(&pm_history->records[i]);
+               }
+               extra_len = sizeof(struct pm_history_np) + (sizeof(struct 
pm_record_np) * pm_history->length);
        }
        if (extra_len) {
                if (extra_len % 2)
@@ -510,11 +565,13 @@ static void mgt_pre_send(struct management_tlv *m, struct 
tlv_extra *extra)
        struct grandmaster_settings_np *gsn;
        struct port_service_stats_np *pssn;
        struct mgmt_clock_description *cd;
+       struct pm_history_np *pm_history;
        struct unicast_master_entry *ume;
        struct subscribe_events_np *sen;
        struct port_properties_np *ppn;
        struct port_hwclock_np *phn;
        struct timePropertiesDS *tp;
+       struct pm_conf_np *pm_conf;
        struct time_status_np *tsn;
        struct port_stats_np *psn;
        struct port_ds_np *pdsnp;
@@ -672,6 +729,29 @@ static void mgt_pre_send(struct management_tlv *m, struct 
tlv_extra *extra)
                HTONL(pwr->networkTimeInaccuracy);
                HTONL(pwr->totalTimeInaccuracy);
                break;
+       case MID_PM_CONF_NP:
+               pm_conf = (struct pm_conf_np *)m->data;
+               HTONS(pm_conf->pm15m_history);
+               HTONS(pm_conf->pm1h_history);
+               HTONS(pm_conf->pm24h_history);
+               break;
+       case MID_PM15M_LAST_NP:
+       case MID_PM15M_CURRENT_NP:
+       case MID_PM15M_HISTORY_NP:
+       case MID_PM1H_LAST_NP:
+       case MID_PM1H_CURRENT_NP:
+       case MID_PM1H_HISTORY_NP:
+       case MID_PM24H_LAST_NP:
+       case MID_PM24H_CURRENT_NP:
+       case MID_PM24H_HISTORY_NP:
+               pm_history = (struct pm_history_np *)m->data;
+               pm_history->portIdentity.portNumber =
+                       htons(pm_history->portIdentity.portNumber);
+               for (i = 0; i < pm_history->length; i++) {
+                       host2net_pm_entry(&pm_history->records[i]);
+               }
+               HTONS(pm_history->length);
+               break;
        }
 }
 
diff --git a/tlv.h b/tlv.h
index 8b51ffd..59e59c1 100644
--- a/tlv.h
+++ b/tlv.h
@@ -129,6 +129,16 @@ enum management_action {
 #define MID_UNICAST_MASTER_TABLE_NP                    0xC008
 #define MID_PORT_HWCLOCK_NP                            0xC009
 #define MID_POWER_PROFILE_SETTINGS_NP                  0xC00A
+#define MID_PM_CONF_NP                                 0xC00B
+#define MID_PM15M_LAST_NP                              0xC00C
+#define MID_PM15M_CURRENT_NP                           0xC00D
+#define MID_PM15M_HISTORY_NP                           0xC00E
+#define MID_PM1H_LAST_NP                               0xC00F
+#define MID_PM1H_CURRENT_NP                            0xC010
+#define MID_PM1H_HISTORY_NP                            0xC011
+#define MID_PM24H_LAST_NP                              0xC012
+#define MID_PM24H_CURRENT_NP                           0xC013
+#define MID_PM24H_HISTORY_NP                           0xC014
 
 /* Management error ID values */
 #define MID_RESPONSE_TOO_BIG                           0x0001
@@ -364,6 +374,31 @@ struct ieee_c37_238_settings_np {
        UInteger32    totalTimeInaccuracy;
 } PACKED;
 
+struct pm_conf_np {
+       UInteger16 pm15m_history;
+       UInteger16 pm1h_history;
+       UInteger16 pm24h_history;
+       UInteger16 dummy;
+} PACKED;
+
+struct pm_record_np {
+       UInteger16 index;
+       UInteger16 flags;
+       PMTimestamp start;
+       PMTimestamp end;
+       struct PortStats stats;
+} PACKED;
+
+struct pm_history_np {
+       struct PortIdentity portIdentity;
+       UInteger16          length;
+       struct pm_record_np records[];
+} PACKED;
+
+#define PM_RECORD_FLAGS_VALID    0x0001
+#define PM_RECORD_FLAGS_COMPLETE 0x0002
+#define PM_RECORD_FLAGS_CURRENT  0x0004
+
 struct msg_interval_req_tlv {
        Enumeration16 type;
        UInteger16    length;
-- 
2.41.0



_______________________________________________
Linuxptp-devel mailing list
Linuxptp-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxptp-devel

Reply via email to