On Wed, Dec 14, 2022 at 05:54:54PM +0100, Theo Buehler wrote:
> On Wed, Dec 14, 2022 at 12:14:55PM +0100, Claudio Jeker wrote:
> > This diff adds per repository statistics, tracks a few more bits like how
> > long it took to sync a repo and finally adds a new openmetrics output.
> > The ometric code is from bgpctl but currently has two hacks in to display
> > the info and stateset types as gauge because node_exporter is unable to
> > properly parse openmetric files.
> >
> > I did not expose additional stats in any of the other output formats, just
> > adjusted them for the new layout. Also I changes the stats from size_t to
> > uint32_t (size_t is not the right type and uint64_t is overkill).
>
> Agreed, uint32_t should be fine.
>
> > The metrics file can be exported via webserver directly to prometheus or
> > as a node_exporter collector textfile.
>
> Regress will no longer compile with this. At the end is a diff that fixes
> regress in case you don't already have a diff for this.
>
> I like it. Below a first pass with a few comments, questions and nits.
Thanks.
> > + if (insert_vap(tree, cas, pas, expires, AFI_IPV4, rp) ||
> > + insert_vap(tree, cas, pas, expires, AFI_IPV6, rp))
>
> I don't think this is correct because of short-circuiting: if a new
> AFI_IPV4 vap is inserted, insert_vap(AFI_IPV6) isn't called.
>
> I'm not sure about the best way to fix this. | instead of || would work
> but is a bit icky inside the if since it looks like a typo that someone
> will 'fix' later. Perhaps something like
>
> new = insert_vap(AFI_IPV4);
> new |= insert_vap(AFI_IPV4);
> if (new)
> repo_stat_inc(rp, RTYPE_ASPA, BOTH);
Indeed. I used new += instead of |=. We just care about new != 0 anyway.
> > + io_read_buf(b, &id, sizeof(id));
> > + rp = repo_byid(id);
>
> Can we assert(rp != NULL) here or should we error? For example, the
> repo_id() call in roa_insert_vrps() could result in a crash otherwise.
I added an assert and added an if (rp != NULL) check in roa_insert_vrps().
> > +int
> > +output_ometric(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
> > + struct vap_tree *vaps, struct stats *st)
> > +{
> > + struct olabels *ol;
> > + const char *keys[4] = { "nodename", "domainname", "release", NULL };
>
> Surely there's a reason... I don't understand "nodename" vs "hostname".
Copied from bgpctl which copied the idea from node_exporter. It seems to
be the modern way of spelling hostname (guess in the cloud everything is a
node and not a host).
> > + case RTYPE_CER:
> > + if (subtype == OK)
> > + rp->stats.certs++;
> > + if (subtype == BGPSEC) {
> > + rp->stats.certs--;
> > + rp->stats.brks++;
> > + }
>
> subtype FAIL isn't handled but called from entity_process()
Fixed.
> > + case UNIQUE:
> > + rp->stats.vrps_uniqs++;
> > + break;
> > + case DEC_UNIQUE:
> > + rp->stats.vrps_uniqs--;
>
> I wonder if we should at least warn on wraparound. This can't currently
> happen, but what if we change the logic down the road?
I think it is overkill. Worst case the stats are off and people will
hopefully notice. If that happens we can redecide to add checks.
Thanks for the regress diff. Will add it to my tree and hopefully not
forget to commit.
Latest version of the rpki-client diff attached.
--
:wq Claudio
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v
retrieving revision 1.28
diff -u -p -r1.28 Makefile
--- Makefile 26 Nov 2022 12:02:36 -0000 1.28
+++ Makefile 30 Nov 2022 09:17:51 -0000
@@ -2,10 +2,11 @@
PROG= rpki-client
SRCS= as.c aspa.c cert.c cms.c crl.c encoding.c filemode.c gbr.c geofeed.c \
- http.c io.c ip.c log.c main.c mft.c mkdir.c output.c output-bgpd.c \
- output-bird.c output-csv.c output-json.c parser.c print.c repo.c \
- roa.c rrdp.c rrdp_delta.c rrdp_notification.c rrdp_snapshot.c \
- rrdp_util.c rsc.c rsync.c tak.c tal.c validate.c x509.c
+ http.c io.c ip.c log.c main.c mft.c mkdir.c ometric.c output.c \
+ output-bgpd.c output-bird.c output-csv.c output-json.c \
+ output-ometric.c parser.c print.c repo.c roa.c rrdp.c rrdp_delta.c \
+ rrdp_notification.c rrdp_snapshot.c rrdp_util.c rsc.c rsync.c tak.c \
+ tal.c validate.c x509.c
MAN= rpki-client.8
LDADD+= -lexpat -ltls -lssl -lcrypto -lutil
Index: aspa.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/aspa.c,v
retrieving revision 1.9
diff -u -p -r1.9 aspa.c
--- aspa.c 29 Nov 2022 20:41:32 -0000 1.9
+++ aspa.c 14 Dec 2022 17:28:57 -0000
@@ -351,9 +351,9 @@ aspa_read(struct ibuf *b)
* Ensure there are no duplicates in the 'providers' array.
* Always compare 'expires': use the soonest expiration moment.
*/
-static void
+static int
insert_vap(struct vap_tree *tree, uint32_t cas, uint32_t pas, time_t expires,
- enum afi afi)
+ enum afi afi, struct repo *rp)
{
struct vap *v, *found;
size_t i;
@@ -371,7 +371,8 @@ insert_vap(struct vap_tree *tree, uint32
v->providers[0] = pas;
v->providersz = 1;
- return;
+ repo_stat_inc(rp, RTYPE_ASPA, STYPE_UNIQUE);
+ return 1;
}
free(v);
@@ -381,7 +382,7 @@ insert_vap(struct vap_tree *tree, uint32
for (i = 0; i < found->providersz; i++) {
if (found->providers[i] == pas)
- return;
+ return 0;
}
found->providers = reallocarray(found->providers,
@@ -389,6 +390,7 @@ insert_vap(struct vap_tree *tree, uint32
if (found->providers == NULL)
err(1, NULL);
found->providers[found->providersz++] = pas;
+ return 1;
}
/*
@@ -397,34 +399,36 @@ insert_vap(struct vap_tree *tree, uint32
* pre-'AFI explosion' deduplicated count.
*/
void
-aspa_insert_vaps(struct vap_tree *tree, struct aspa *aspa, size_t *vaps,
- size_t *uniqs)
+aspa_insert_vaps(struct vap_tree *tree, struct aspa *aspa, struct repo *rp)
{
size_t i;
uint32_t cas, pas;
time_t expires;
+ int new;
cas = aspa->custasid;
expires = aspa->expires;
- *uniqs += aspa->providersz;
+ repo_stat_inc(rp, RTYPE_ASPA, STYPE_TOTAL);
for (i = 0; i < aspa->providersz; i++) {
pas = aspa->providers[i].as;
switch (aspa->providers[i].afi) {
case AFI_IPV4:
- insert_vap(tree, cas, pas, expires, AFI_IPV4);
- (*vaps)++;
+ if (insert_vap(tree, cas, pas, expires, AFI_IPV4, rp))
+ repo_stat_inc(rp, RTYPE_ASPA, STYPE_ONLY_IPV4);
break;
case AFI_IPV6:
- insert_vap(tree, cas, pas, expires, AFI_IPV6);
- (*vaps)++;
+ if (insert_vap(tree, cas, pas, expires, AFI_IPV6, rp))
+ repo_stat_inc(rp, RTYPE_ASPA, STYPE_ONLY_IPV6);
break;
default:
- insert_vap(tree, cas, pas, expires, AFI_IPV4);
- insert_vap(tree, cas, pas, expires, AFI_IPV6);
- *vaps += 2;
+ new = insert_vap(tree, cas, pas, expires, AFI_IPV4, rp);
+ new += insert_vap(tree, cas, pas, expires, AFI_IPV6,
+ rp);
+ if (new != 0)
+ repo_stat_inc(rp, RTYPE_ASPA, STYPE_BOTH);
break;
}
}
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
retrieving revision 1.163
diff -u -p -r1.163 extern.h
--- extern.h 14 Dec 2022 10:34:49 -0000 1.163
+++ extern.h 14 Dec 2022 17:28:27 -0000
@@ -376,6 +376,7 @@ struct vrp {
RB_ENTRY(vrp) entry;
struct ip_addr addr;
int talid; /* covered by which TAL */
+ unsigned int repoid;
uint32_t asid;
enum afi afi;
unsigned char maxlength;
@@ -493,6 +494,20 @@ struct entity {
};
TAILQ_HEAD(entityq, entity);
+enum stype {
+ STYPE_OK,
+ STYPE_FAIL,
+ STYPE_INVALID,
+ STYPE_STALE,
+ STYPE_BGPSEC,
+ STYPE_TOTAL,
+ STYPE_UNIQUE,
+ STYPE_DEC_UNIQUE,
+ STYPE_ONLY_IPV4,
+ STYPE_ONLY_IPV6,
+ STYPE_BOTH,
+};
+
struct repo;
struct filepath;
RB_HEAD(filepath_tree, filepath);
@@ -501,41 +516,50 @@ RB_HEAD(filepath_tree, filepath);
/*
* Statistics collected during run-time.
*/
+struct repostats {
+ uint32_t certs; /* certificates */
+ uint32_t certs_fail; /* invalid certificate */
+ uint32_t mfts; /* total number of manifests */
+ uint32_t mfts_fail; /* failing syntactic parse */
+ uint32_t mfts_stale; /* stale manifests */
+ uint32_t roas; /* route origin authorizations */
+ uint32_t roas_fail; /* failing syntactic parse */
+ uint32_t roas_invalid; /* invalid resources */
+ uint32_t aspas; /* ASPA objects */
+ uint32_t aspas_fail; /* ASPA objects failing syntactic parse */
+ uint32_t aspas_invalid; /* ASPAs with invalid customerASID */
+ uint32_t brks; /* number of BGPsec Router Key (BRK) certs */
+ uint32_t crls; /* revocation lists */
+ uint32_t gbrs; /* ghostbuster records */
+ uint32_t taks; /* signed TAL objects */
+ uint32_t vaps; /* total number of Validated ASPA Payloads */
+ uint32_t vaps_uniqs; /* total number of unique VAPs */
+ uint32_t vaps_pas; /* total number of providers */
+ uint32_t vaps_pas4; /* total number of IPv4 only providers */
+ uint32_t vaps_pas6; /* total number of IPv6 only providers */
+ uint32_t vrps; /* total number of Validated ROA Payloads */
+ uint32_t vrps_uniqs; /* number of unique vrps */
+ struct timespec sync_time; /* time to sync repo */
+};
+
struct stats {
- size_t tals; /* total number of locators */
- size_t mfts; /* total number of manifests */
- size_t mfts_fail; /* failing syntactic parse */
- size_t mfts_stale; /* stale manifests */
- size_t certs; /* certificates */
- size_t certs_fail; /* invalid certificate */
- size_t roas; /* route origin authorizations */
- size_t roas_fail; /* failing syntactic parse */
- size_t roas_invalid; /* invalid resources */
- size_t repos; /* repositories */
- size_t rsync_repos; /* synced rsync repositories */
- size_t rsync_fails; /* failed rsync repositories */
- size_t http_repos; /* synced http repositories */
- size_t http_fails; /* failed http repositories */
- size_t rrdp_repos; /* synced rrdp repositories */
- size_t rrdp_fails; /* failed rrdp repositories */
- size_t crls; /* revocation lists */
- size_t gbrs; /* ghostbuster records */
- size_t taks; /* signed TAL objects */
- size_t aspas; /* ASPA objects */
- size_t aspas_fail; /* ASPA objects failing syntactic parse */
- size_t aspas_invalid; /* ASPAs with invalid customerASID */
- size_t vaps; /* total number of Validated ASPA Payloads */
- size_t vaps_uniqs; /* total number of unique VAPs */
- size_t vrps; /* total number of vrps */
- size_t uniqs; /* number of unique vrps */
- size_t del_files; /* number of files removed in cleanup */
- size_t extra_files; /* number of superfluous files */
- size_t del_dirs; /* number of directories removed in cleanup */
- size_t brks; /* number of BGPsec Router Key (BRK) certificates */
- size_t skiplistentries; /* number of skiplist entries */
- struct timespec elapsed_time;
- struct timespec user_time;
- struct timespec system_time;
+ uint32_t tals; /* total number of locators */
+ uint32_t repos; /* repositories */
+ uint32_t rsync_repos; /* synced rsync repositories */
+ uint32_t rsync_fails; /* failed rsync repositories */
+ uint32_t http_repos; /* synced http repositories */
+ uint32_t http_fails; /* failed http repositories */
+ uint32_t rrdp_repos; /* synced rrdp repositories */
+ uint32_t rrdp_fails; /* failed rrdp repositories */
+ uint32_t del_files; /* number of files removed in cleanup */
+ uint32_t extra_files; /* number of superfluous files */
+ uint32_t del_dirs; /* number of dirs removed in cleanup */
+ uint32_t skiplistentries; /* number of skiplist entries */
+
+ struct repostats repo_stats;
+ struct timespec elapsed_time;
+ struct timespec user_time;
+ struct timespec system_time;
};
struct ibuf;
@@ -547,6 +571,7 @@ extern int filemode;
extern const char *tals[];
extern const char *taldescs[];
extern unsigned int talrepocnt[];
+extern struct repostats talstats[];
extern int talsz;
/* Routines for RPKI entities. */
@@ -580,8 +605,8 @@ void roa_free(struct roa *);
struct roa *roa_parse(X509 **, const char *, const unsigned char *,
size_t);
struct roa *roa_read(struct ibuf *);
-void roa_insert_vrps(struct vrp_tree *, struct roa *, size_t *,
- size_t *);
+void roa_insert_vrps(struct vrp_tree *, struct roa *,
+ struct repo *);
void gbr_free(struct gbr *);
struct gbr *gbr_parse(X509 **, const char *, const unsigned char *,
@@ -602,8 +627,8 @@ struct tak *tak_read(struct ibuf *);
void aspa_buffer(struct ibuf *, const struct aspa *);
void aspa_free(struct aspa *);
-void aspa_insert_vaps(struct vap_tree *, struct aspa *, size_t *,
- size_t *);
+void aspa_insert_vaps(struct vap_tree *, struct aspa *,
+ struct repo *);
struct aspa *aspa_parse(X509 **, const char *, const unsigned char *,
size_t);
struct aspa *aspa_read(struct ibuf *);
@@ -703,11 +728,19 @@ int rrdp_handle_file(unsigned int, enu
char *repo_basedir(const struct repo *, int);
unsigned int repo_id(const struct repo *);
const char *repo_uri(const struct repo *);
+void repo_fetch_uris(const struct repo *, const char **,
+ const char **);
+int repo_synced(const struct repo *);
+int repo_talid(const struct repo *);
struct repo *ta_lookup(int, struct tal *);
struct repo *repo_lookup(int, const char *, const char *);
struct repo *repo_byid(unsigned int);
int repo_queued(struct repo *, struct entity *);
void repo_cleanup(struct filepath_tree *, int);
+int repo_check_timeout(int);
+void repo_stat_inc(struct repo *, enum rtype, enum stype);
+void repo_stats_collect(void (*)(const struct repo *,
+ const struct repostats *, void *), void *);
void repo_free(void);
void rsync_finish(unsigned int, int);
@@ -722,7 +755,6 @@ void rrdp_fetch(unsigned int, const ch
struct rrdp_session *);
void rrdp_abort(unsigned int);
void rrdp_http_done(unsigned int, enum http_result, const char *);
-int repo_check_timeout(int);
/* Logging (though really used for OpenSSL errors). */
@@ -797,6 +829,7 @@ extern int outformats;
#define FORMAT_BIRD 0x02
#define FORMAT_CSV 0x04
#define FORMAT_JSON 0x08
+#define FORMAT_OMETRIC 0x10
int outputfiles(struct vrp_tree *v, struct brk_tree *b,
struct vap_tree *, struct stats *);
@@ -812,6 +845,8 @@ int output_bird2(FILE *, struct vrp_tr
int output_csv(FILE *, struct vrp_tree *, struct brk_tree *,
struct vap_tree *, struct stats *);
int output_json(FILE *, struct vrp_tree *, struct brk_tree *,
+ struct vap_tree *, struct stats *);
+int output_ometric(FILE *, struct vrp_tree *, struct brk_tree *,
struct vap_tree *, struct stats *);
void logx(const char *fmt, ...)
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
retrieving revision 1.228
diff -u -p -r1.228 main.c
--- main.c 14 Dec 2022 10:34:49 -0000 1.228
+++ main.c 14 Dec 2022 17:30:18 -0000
@@ -51,6 +51,7 @@
const char *tals[TALSZ_MAX];
const char *taldescs[TALSZ_MAX];
unsigned int talrepocnt[TALSZ_MAX];
+struct repostats talstats[TALSZ_MAX];
int talsz;
size_t entity_queue;
@@ -535,7 +536,9 @@ entity_process(struct ibuf *b, struct st
struct mft *mft;
struct roa *roa;
struct aspa *aspa;
+ struct repo *rp;
char *file;
+ unsigned int id;
int c;
/*
@@ -556,6 +559,10 @@ entity_process(struct ibuf *b, struct st
goto done;
}
+ io_read_buf(b, &id, sizeof(id));
+ rp = repo_byid(id);
+ assert(rp != NULL);
+ repo_stat_inc(rp, type, STYPE_OK);
switch (type) {
case RTYPE_TAL:
st->tals++;
@@ -564,10 +571,9 @@ entity_process(struct ibuf *b, struct st
tal_free(tal);
break;
case RTYPE_CER:
- st->certs++;
io_read_buf(b, &c, sizeof(c));
if (c == 0) {
- st->certs_fail++;
+ repo_stat_inc(rp, type, STYPE_FAIL);
break;
}
cert = cert_read(b);
@@ -577,65 +583,58 @@ entity_process(struct ibuf *b, struct st
break;
case CERT_PURPOSE_BGPSEC_ROUTER:
cert_insert_brks(brktree, cert);
- st->brks++;
+ repo_stat_inc(rp, type, STYPE_BGPSEC);
break;
default:
- st->certs_fail++;
+ errx(1, "unexpected cert purpose received");
break;
}
cert_free(cert);
break;
case RTYPE_MFT:
- st->mfts++;
io_read_buf(b, &c, sizeof(c));
if (c == 0) {
- st->mfts_fail++;
+ repo_stat_inc(rp, type, STYPE_FAIL);
break;
}
mft = mft_read(b);
if (!mft->stale)
queue_add_from_mft(mft);
else
- st->mfts_stale++;
+ repo_stat_inc(rp, type, STYPE_STALE);
mft_free(mft);
break;
case RTYPE_CRL:
- st->crls++;
break;
case RTYPE_ROA:
- st->roas++;
io_read_buf(b, &c, sizeof(c));
if (c == 0) {
- st->roas_fail++;
+ repo_stat_inc(rp, type, STYPE_FAIL);
break;
}
roa = roa_read(b);
if (roa->valid)
- roa_insert_vrps(tree, roa, &st->vrps, &st->uniqs);
+ roa_insert_vrps(tree, roa, rp);
else
- st->roas_invalid++;
+ repo_stat_inc(rp, type, STYPE_INVALID);
roa_free(roa);
break;
case RTYPE_GBR:
- st->gbrs++;
break;
case RTYPE_ASPA:
- st->aspas++;
io_read_buf(b, &c, sizeof(c));
if (c == 0) {
- st->aspas_fail++;
+ repo_stat_inc(rp, type, STYPE_FAIL);
break;
}
aspa = aspa_read(b);
if (aspa->valid)
- aspa_insert_vaps(vaptree, aspa, &st->vaps,
- &st->vaps_uniqs);
+ aspa_insert_vaps(vaptree, aspa, rp);
else
- st->aspas_invalid++;
+ repo_stat_inc(rp, type, STYPE_INVALID);
aspa_free(aspa);
break;
case RTYPE_TAK:
- st->taks++;
break;
case RTYPE_FILE:
break;
@@ -703,6 +702,40 @@ rrdp_process(struct ibuf *b)
}
}
+static void
+sum_stats(const struct repo *rp, const struct repostats *in, void *arg)
+{
+ struct repostats *out = arg;
+
+ if (rp != NULL)
+ sum_stats(NULL, in, &talstats[repo_talid(rp)]);
+
+ out->mfts += in->mfts;
+ out->mfts_fail += in->mfts_fail;
+ out->mfts_stale += in->mfts_stale;
+ out->certs += in->certs;
+ out->certs_fail += in->certs_fail;
+ out->roas += in->roas;
+ out->roas_fail += in->roas_fail;
+ out->roas_invalid += in->roas_invalid;
+ out->aspas += in->aspas;
+ out->aspas_fail += in->aspas_fail;
+ out->aspas_invalid += in->aspas_invalid;
+ out->brks += in->brks;
+ out->crls += in->crls;
+ out->gbrs += in->gbrs;
+ out->taks += in->taks;
+ out->vrps += in->vrps;
+ out->vrps_uniqs += in->vrps_uniqs;
+ out->vaps += in->vaps;
+ out->vaps_uniqs += in->vaps_uniqs;
+ out->vaps_pas += in->vaps_pas;
+ out->vaps_pas4 += in->vaps_pas4;
+ out->vaps_pas6 += in->vaps_pas6;
+
+ timespecadd(&in->sync_time, &out->sync_time, &out->sync_time);
+}
+
/*
* Assign filenames ending in ".tal" in "/etc/rpki" into "tals",
* returning the number of files found and filled-in.
@@ -909,7 +942,7 @@ main(int argc, char *argv[])
"proc exec unveil", NULL) == -1)
err(1, "pledge");
- while ((c = getopt(argc, argv, "b:Bcd:e:fH:jnorRs:S:t:T:vV")) != -1)
+ while ((c = getopt(argc, argv, "b:Bcd:e:fH:jmnorRs:S:t:T:vV")) != -1)
switch (c) {
case 'b':
bind_addr = optarg;
@@ -937,6 +970,9 @@ main(int argc, char *argv[])
case 'j':
outformats |= FORMAT_JSON;
break;
+ case 'm':
+ outformats |= FORMAT_OMETRIC;
+ break;
case 'n':
noop = 1;
break;
@@ -1344,6 +1380,8 @@ main(int argc, char *argv[])
if (fchdir(outdirfd) == -1)
err(1, "fchdir output dir");
+ repo_stats_collect(sum_stats, &stats.repo_stats);
+
if (outputfiles(&vrps, &brks, &vaps, &stats))
rc = 1;
@@ -1352,26 +1390,31 @@ main(int argc, char *argv[])
(long long)stats.elapsed_time.tv_sec,
(long long)stats.user_time.tv_sec,
(long long)stats.system_time.tv_sec);
- printf("Skiplist entries: %zu\n", stats.skiplistentries);
- printf("Route Origin Authorizations: %zu (%zu failed parse, %zu "
- "invalid)\n", stats.roas, stats.roas_fail, stats.roas_invalid);
- printf("AS Provider Attestations: %zu (%zu failed parse, %zu "
- "invalid)\n", stats.aspas, stats.aspas_fail, stats.aspas_invalid);
- printf("BGPsec Router Certificates: %zu\n", stats.brks);
- printf("Certificates: %zu (%zu invalid)\n",
- stats.certs, stats.certs_fail);
- printf("Trust Anchor Locators: %zu (%zu invalid)\n",
+ printf("Skiplist entries: %u\n", stats.skiplistentries);
+ printf("Route Origin Authorizations: %u (%u failed parse, %u "
+ "invalid)\n", stats.repo_stats.roas, stats.repo_stats.roas_fail,
+ stats.repo_stats.roas_invalid);
+ printf("AS Provider Attestations: %u (%u failed parse, %u "
+ "invalid)\n", stats.repo_stats.aspas, stats.repo_stats.aspas_fail,
+ stats.repo_stats.aspas_invalid);
+ printf("BGPsec Router Certificates: %u\n", stats.repo_stats.brks);
+ printf("Certificates: %u (%u invalid)\n",
+ stats.repo_stats.certs, stats.repo_stats.certs_fail);
+ printf("Trust Anchor Locators: %u (%u invalid)\n",
stats.tals, talsz - stats.tals);
- printf("Manifests: %zu (%zu failed parse, %zu stale)\n",
- stats.mfts, stats.mfts_fail, stats.mfts_stale);
- printf("Certificate revocation lists: %zu\n", stats.crls);
- printf("Ghostbuster records: %zu\n", stats.gbrs);
- printf("Trust Anchor Keys: %zu\n", stats.taks);
- printf("Repositories: %zu\n", stats.repos);
- printf("Cleanup: removed %zu files, %zu directories, %zu superfluous\n",
+ printf("Manifests: %u (%u failed parse, %u stale)\n",
+ stats.repo_stats.mfts, stats.repo_stats.mfts_fail,
+ stats.repo_stats.mfts_stale);
+ printf("Certificate revocation lists: %u\n", stats.repo_stats.crls);
+ printf("Ghostbuster records: %u\n", stats.repo_stats.gbrs);
+ printf("Trust Anchor Keys: %u\n", stats.repo_stats.taks);
+ printf("Repositories: %u\n", stats.repos);
+ printf("Cleanup: removed %u files, %u directories, %u superfluous\n",
stats.del_files, stats.del_dirs, stats.extra_files);
- printf("VRP Entries: %zu (%zu unique)\n", stats.vrps, stats.uniqs);
- printf("VAP Entries: %zu (%zu unique)\n", stats.vaps, stats.vaps_uniqs);
+ printf("VRP Entries: %u (%u unique)\n", stats.repo_stats.vrps,
+ stats.repo_stats.vrps_uniqs);
+ printf("VAP Entries: %u (%u unique)\n", stats.repo_stats.vaps,
+ stats.repo_stats.vaps_uniqs);
/* Memory cleanup. */
repo_free();
@@ -1380,7 +1423,7 @@ main(int argc, char *argv[])
usage:
fprintf(stderr,
- "usage: rpki-client [-BcjnoRrVv] [-b sourceaddr] [-d cachedir]"
+ "usage: rpki-client [-BcjmnoRrVv] [-b sourceaddr] [-d cachedir]"
" [-e rsync_prog]\n"
" [-H fqdn] [-S skiplist] [-s timeout] [-T table]"
" [-t tal]\n"
Index: ometric.c
===================================================================
RCS file: ometric.c
diff -N ometric.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ometric.c 14 Dec 2022 09:01:18 -0000
@@ -0,0 +1,527 @@
+/* $OpenBSD: ometric.c,v 1.8 2022/12/12 09:51:04 claudio Exp $ */
+
+/*
+ * Copyright (c) 2022 Claudio Jeker <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ometric.h"
+
+struct olabel {
+ STAILQ_ENTRY(olabel) entry;
+ const char *key;
+ char *value;
+};
+
+struct olabels {
+ STAILQ_HEAD(, olabel) labels;
+ struct olabels *next;
+ int refcnt;
+};
+
+enum ovalue_type {
+ OVT_INTEGER,
+ OVT_DOUBLE,
+ OVT_TIMESPEC,
+};
+
+struct ovalue {
+ STAILQ_ENTRY(ovalue) entry;
+ struct olabels *labels;
+ union {
+ unsigned long long i;
+ double f;
+ struct timespec ts;
+ } value;
+ enum ovalue_type valtype;
+};
+
+STAILQ_HEAD(ovalues, ovalue);
+
+struct ometric {
+ STAILQ_ENTRY(ometric) entry;
+ struct ovalues vals;
+ const char *name;
+ const char *help;
+ const char *const *stateset;
+ size_t setsize;
+ enum ometric_type type;
+};
+
+STAILQ_HEAD(, ometric) ometrics = STAILQ_HEAD_INITIALIZER(ometrics);
+
+static const char *suffixes[] = { "_total", "_created", "_count",
+ "_sum", "_bucket", "_gcount", "_gsum", "_info",
+};
+
+/*
+ * Return true if name has one of the above suffixes.
+ */
+static int
+strsuffix(const char *name)
+{
+ const char *suffix;
+ size_t i;
+
+ suffix = strrchr(name, '_');
+ if (suffix == NULL)
+ return 0;
+ for (i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) {
+ if (strcmp(suffix, suffixes[i]) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static void
+ometric_check(const char *name)
+{
+ struct ometric *om;
+
+ if (strsuffix(name))
+ errx(1, "reserved name suffix used: %s", name);
+ STAILQ_FOREACH(om, &ometrics, entry)
+ if (strcmp(name, om->name) == 0)
+ errx(1, "duplicate name: %s", name);
+}
+
+/*
+ * Allocate and return new ometric. The name and help string need to remain
+ * valid until the ometric is freed. Normally constant strings should be used.
+ */
+struct ometric *
+ometric_new(enum ometric_type type, const char *name, const char *help)
+{
+ struct ometric *om;
+
+ ometric_check(name);
+
+ if ((om = calloc(1, sizeof(*om))) == NULL)
+ err(1, NULL);
+
+ om->name = name;
+ om->help = help;
+ om->type = type;
+ STAILQ_INIT(&om->vals);
+
+ STAILQ_INSERT_TAIL(&ometrics, om, entry);
+
+ return om;
+}
+
+/*
+ * Same as above but for a stateset. The states is an array of constant strings
+ * with statecnt elements. The states, name and help pointers need to remain
+ * valid until the ometric is freed.
+ */
+struct ometric *
+ometric_new_state(const char * const *states, size_t statecnt, const char
*name,
+ const char *help)
+{
+ struct ometric *om;
+
+ ometric_check(name);
+
+ if ((om = calloc(1, sizeof(*om))) == NULL)
+ err(1, NULL);
+
+ om->name = name;
+ om->help = help;
+ om->type = OMT_STATESET;
+ om->stateset = states;
+ om->setsize = statecnt;
+ STAILQ_INIT(&om->vals);
+
+ STAILQ_INSERT_TAIL(&ometrics, om, entry);
+
+ return om;
+}
+
+void
+ometric_free_all(void)
+{
+ struct ometric *om;
+ struct ovalue *ov;
+
+ while ((om = STAILQ_FIRST(&ometrics)) != NULL) {
+ STAILQ_REMOVE_HEAD(&ometrics, entry);
+ while ((ov = STAILQ_FIRST(&om->vals)) != NULL) {
+ STAILQ_REMOVE_HEAD(&om->vals, entry);
+ olabels_free(ov->labels);
+ free(ov);
+ }
+ free(om);
+ }
+}
+
+static struct olabels *
+olabels_ref(struct olabels *ol)
+{
+ struct olabels *x = ol;
+
+ while (x != NULL) {
+ x->refcnt++;
+ x = x->next;
+ }
+
+ return ol;
+}
+
+/*
+ * Create a new set of labels based on keys and values arrays.
+ * keys must end in a NULL element. values needs to hold as many elements
+ * but the elements can be NULL. values are copied for the olabel but
+ * keys needs to point to constant memory.
+ */
+struct olabels *
+olabels_new(const char * const *keys, const char **values)
+{
+ struct olabels *ol;
+ struct olabel *l;
+
+ if ((ol = malloc(sizeof(*ol))) == NULL)
+ err(1, NULL);
+ STAILQ_INIT(&ol->labels);
+ ol->refcnt = 1;
+ ol->next = NULL;
+
+ while (*keys != NULL) {
+ if (*values && **values != '\0') {
+ if ((l = malloc(sizeof(*l))) == NULL)
+ err(1, NULL);
+ l->key = *keys;
+ if ((l->value = strdup(*values)) == NULL)
+ err(1, NULL);
+ STAILQ_INSERT_TAIL(&ol->labels, l, entry);
+ }
+
+ keys++;
+ values++;
+ }
+
+ return ol;
+}
+
+/*
+ * Free olables once nothing uses them anymore.
+ */
+void
+olabels_free(struct olabels *ol)
+{
+ struct olabels *next;
+ struct olabel *l;
+
+ for ( ; ol != NULL; ol = next) {
+ next = ol->next;
+
+ if (--ol->refcnt == 0) {
+ while ((l = STAILQ_FIRST(&ol->labels)) != NULL) {
+ STAILQ_REMOVE_HEAD(&ol->labels, entry);
+ free(l->value);
+ free(l);
+ }
+ free(ol);
+ }
+ }
+}
+
+/*
+ * Add one extra label onto the label stack. Once no longer used the
+ * value needs to be freed with olabels_free().
+ */
+static struct olabels *
+olabels_add_extras(struct olabels *ol, const char **keys, const char **values)
+{
+ struct olabels *new;
+
+ new = olabels_new(keys, values);
+ new->next = olabels_ref(ol);
+
+ return new;
+}
+
+/*
+ * Output function called last.
+ */
+static const char *
+ometric_type(enum ometric_type type)
+{
+ switch (type) {
+ case OMT_GAUGE:
+ return "gauge";
+ case OMT_COUNTER:
+ return "counter";
+ case OMT_STATESET:
+ /* return "stateset"; node_exporter does not like this type */
+ return "gauge";
+ case OMT_HISTOGRAM:
+ return "histogram";
+ case OMT_SUMMARY:
+ return "summary";
+ case OMT_INFO:
+ /* return "info"; node_exporter does not like this type */
+ return "gauge";
+ default:
+ return "unknown";
+ }
+}
+
+static int
+ometric_output_labels(FILE *out, const struct olabels *ol)
+{
+ struct olabel *l;
+ const char *comma = "";
+
+ if (ol == NULL)
+ return fprintf(out, " ");
+
+ if (fprintf(out, "{") < 0)
+ return -1;
+
+ while (ol != NULL) {
+ STAILQ_FOREACH(l, &ol->labels, entry) {
+ if (fprintf(out, "%s%s=\"%s\"", comma, l->key,
+ l->value) < 0)
+ return -1;
+ comma = ",";
+ }
+ ol = ol->next;
+ }
+
+ return fprintf(out, "} ");
+}
+
+static int
+ometric_output_value(FILE *out, const struct ovalue *ov)
+{
+ switch (ov->valtype) {
+ case OVT_INTEGER:
+ return fprintf(out, "%llu", ov->value.i);
+ case OVT_DOUBLE:
+ return fprintf(out, "%g", ov->value.f);
+ case OVT_TIMESPEC:
+ return fprintf(out, "%lld.%09ld",
+ (long long)ov->value.ts.tv_sec, ov->value.ts.tv_nsec);
+ }
+ return -1;
+}
+
+static int
+ometric_output_name(FILE *out, const struct ometric *om)
+{
+ const char *suffix;
+
+ switch (om->type) {
+ case OMT_COUNTER:
+ suffix = "_total";
+ break;
+ case OMT_INFO:
+ suffix = "_info";
+ break;
+ default:
+ suffix = "";
+ break;
+ }
+ return fprintf(out, "%s%s", om->name, suffix);
+}
+
+/*
+ * Output all metric values with TYPE and optional HELP strings.
+ */
+int
+ometric_output_all(FILE *out)
+{
+ struct ometric *om;
+ struct ovalue *ov;
+
+ STAILQ_FOREACH(om, &ometrics, entry) {
+ if (om->help)
+ if (fprintf(out, "# HELP %s %s\n", om->name,
+ om->help) < 0)
+ return -1;
+
+ if (fprintf(out, "# TYPE %s %s\n", om->name,
+ ometric_type(om->type)) < 0)
+ return -1;
+
+ STAILQ_FOREACH(ov, &om->vals, entry) {
+ if (ometric_output_name(out, om) < 0)
+ return -1;
+ if (ometric_output_labels(out, ov->labels) < 0)
+ return -1;
+ if (ometric_output_value(out, ov) < 0)
+ return -1;
+ if (fprintf(out, "\n") < 0)
+ return -1;
+ }
+ }
+
+ if (fprintf(out, "# EOF\n") < 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * Value setters
+ */
+static void
+ometric_set_int_value(struct ometric *om, uint64_t val, struct olabels *ol)
+{
+ struct ovalue *ov;
+
+ if ((ov = malloc(sizeof(*ov))) == NULL)
+ err(1, NULL);
+
+ ov->value.i = val;
+ ov->valtype = OVT_INTEGER;
+ ov->labels = olabels_ref(ol);
+
+ STAILQ_INSERT_TAIL(&om->vals, ov, entry);
+}
+
+/*
+ * Set an integer value with label ol. ol can be NULL.
+ */
+void
+ometric_set_int(struct ometric *om, uint64_t val, struct olabels *ol)
+{
+ if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
+ errx(1, "%s incorrect ometric type", __func__);
+
+ ometric_set_int_value(om, val, ol);
+}
+
+/*
+ * Set a floating point value with label ol. ol can be NULL.
+ */
+void
+ometric_set_float(struct ometric *om, double val, struct olabels *ol)
+{
+ struct ovalue *ov;
+
+ if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
+ errx(1, "%s incorrect ometric type", __func__);
+
+ if ((ov = malloc(sizeof(*ov))) == NULL)
+ err(1, NULL);
+
+ ov->value.f = val;
+ ov->valtype = OVT_DOUBLE;
+ ov->labels = olabels_ref(ol);
+
+ STAILQ_INSERT_TAIL(&om->vals, ov, entry);
+}
+
+/*
+ * Set an timespec value with label ol. ol can be NULL.
+ */
+void
+ometric_set_timespec(struct ometric *om, const struct timespec *ts,
+ struct olabels *ol)
+{
+ struct ovalue *ov;
+
+ if (om->type != OMT_GAUGE)
+ errx(1, "%s incorrect ometric type", __func__);
+
+ if ((ov = malloc(sizeof(*ov))) == NULL)
+ err(1, NULL);
+
+ ov->value.ts = *ts;
+ ov->valtype = OVT_TIMESPEC;
+ ov->labels = olabels_ref(ol);
+
+ STAILQ_INSERT_TAIL(&om->vals, ov, entry);
+}
+
+/*
+ * Add an info value (which is the value 1 but with extra key-value pairs).
+ */
+void
+ometric_set_info(struct ometric *om, const char **keys, const char **values,
+ struct olabels *ol)
+{
+ struct olabels *extra = NULL;
+
+ if (om->type != OMT_INFO)
+ errx(1, "%s incorrect ometric type", __func__);
+
+ if (keys != NULL)
+ extra = olabels_add_extras(ol, keys, values);
+
+ ometric_set_int_value(om, 1, extra != NULL ? extra : ol);
+ olabels_free(extra);
+}
+
+/*
+ * Set a stateset to one of its states.
+ */
+void
+ometric_set_state(struct ometric *om, const char *state, struct olabels *ol)
+{
+ struct olabels *extra;
+ size_t i;
+ int val;
+
+ if (om->type != OMT_STATESET)
+ errx(1, "%s incorrect ometric type", __func__);
+
+ for (i = 0; i < om->setsize; i++) {
+ if (strcasecmp(state, om->stateset[i]) == 0)
+ val = 1;
+ else
+ val = 0;
+
+ extra = olabels_add_extras(ol, OKV(om->name),
+ OKV(om->stateset[i]));
+ ometric_set_int_value(om, val, extra);
+ olabels_free(extra);
+ }
+}
+
+/*
+ * Set a value with an extra label, the key should be a constant string while
+ * the value is copied into the extra label.
+ */
+void
+ometric_set_int_with_labels(struct ometric *om, uint64_t val,
+ const char **keys, const char **values, struct olabels *ol)
+{
+ struct olabels *extra;
+
+ extra = olabels_add_extras(ol, keys, values);
+ ometric_set_int(om, val, extra);
+ olabels_free(extra);
+}
+
+void
+ometric_set_timespec_with_labels(struct ometric *om, struct timespec *ts,
+ const char **keys, const char **values, struct olabels *ol)
+{
+ struct olabels *extra;
+
+ extra = olabels_add_extras(ol, keys, values);
+ ometric_set_timespec(om, ts, extra);
+ olabels_free(extra);
+}
Index: ometric.h
===================================================================
RCS file: ometric.h
diff -N ometric.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ometric.h 13 Dec 2022 11:15:22 -0000
@@ -0,0 +1,53 @@
+/* $OpenBSD: ometric.h,v 1.5 2022/12/12 09:51:04 claudio Exp $ */
+
+/*
+ * Copyright (c) 2022 Claudio Jeker <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+enum ometric_type {
+ OMT_UNKNOWN,
+ OMT_GAUGE,
+ OMT_COUNTER,
+ OMT_STATESET,
+ OMT_HISTOGRAM,
+ OMT_SUMMARY,
+ OMT_INFO,
+};
+
+struct ometric;
+struct olabels;
+
+struct ometric *ometric_new(enum ometric_type, const char *, const char *);
+struct ometric *ometric_new_state(const char * const *, size_t, const char *,
+ const char *);
+void ometric_free_all(void);
+struct olabels *olabels_new(const char * const *, const char **);
+void olabels_free(struct olabels *);
+
+int ometric_output_all(FILE *);
+
+/* functions to set gauge and counter metrics */
+void ometric_set_int(struct ometric *, uint64_t, struct olabels *);
+void ometric_set_float(struct ometric *, double, struct olabels *);
+void ometric_set_timespec(struct ometric *, const struct timespec *,
+ struct olabels *);
+void ometric_set_info(struct ometric *, const char **, const char **,
+ struct olabels *);
+void ometric_set_state(struct ometric *, const char *, struct olabels *);
+void ometric_set_int_with_labels(struct ometric *, uint64_t, const char **,
+ const char **, struct olabels *);
+void ometric_set_timespec_with_labels(struct ometric *, struct timespec *,
+ const char **, const char **, struct olabels *);
+#define OKV(...) (const char *[]){ __VA_ARGS__, NULL }
Index: output-json.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v
retrieving revision 1.29
diff -u -p -r1.29 output-json.c
--- output-json.c 2 Nov 2022 12:43:02 -0000 1.29
+++ output-json.c 1 Dec 2022 14:58:11 -0000
@@ -43,24 +43,31 @@ outputheader_json(FILE *out, struct stat
"\t\t\"elapsedtime\": \"%lld\",\n"
"\t\t\"usertime\": \"%lld\",\n"
"\t\t\"systemtime\": \"%lld\",\n"
- "\t\t\"roas\": %zu,\n"
- "\t\t\"failedroas\": %zu,\n"
- "\t\t\"invalidroas\": %zu,\n"
- "\t\t\"aspas\": %zu,\n"
- "\t\t\"failedaspas\": %zu,\n"
- "\t\t\"invalidaspas\": %zu,\n"
- "\t\t\"bgpsec_pubkeys\": %zu,\n"
- "\t\t\"certificates\": %zu,\n"
- "\t\t\"invalidcertificates\": %zu,\n"
- "\t\t\"taks\": %zu,\n"
- "\t\t\"tals\": %zu,\n"
- "\t\t\"invalidtals\": %zu,\n"
+ "\t\t\"roas\": %u,\n"
+ "\t\t\"failedroas\": %u,\n"
+ "\t\t\"invalidroas\": %u,\n"
+ "\t\t\"aspas\": %u,\n"
+ "\t\t\"failedaspas\": %u,\n"
+ "\t\t\"invalidaspas\": %u,\n"
+ "\t\t\"bgpsec_pubkeys\": %u,\n"
+ "\t\t\"certificates\": %u,\n"
+ "\t\t\"invalidcertificates\": %u,\n"
+ "\t\t\"taks\": %u,\n"
+ "\t\t\"tals\": %u,\n"
+ "\t\t\"invalidtals\": %u,\n"
"\t\t\"talfiles\": [\n",
hn, tbuf, (long long)st->elapsed_time.tv_sec,
(long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
- st->roas, st->roas_fail, st->roas_invalid,
- st->aspas, st->aspas_fail, st->aspas_invalid,
- st->brks, st->certs, st->certs_fail, st->taks,
+ st->repo_stats.roas,
+ st->repo_stats.roas_fail,
+ st->repo_stats.roas_invalid,
+ st->repo_stats.aspas,
+ st->repo_stats.aspas_fail,
+ st->repo_stats.aspas_invalid,
+ st->repo_stats.brks,
+ st->repo_stats.certs,
+ st->repo_stats.certs_fail,
+ st->repo_stats.taks,
st->tals, talsz - st->tals) < 0)
return -1;
@@ -73,27 +80,33 @@ outputheader_json(FILE *out, struct stat
if (fprintf(out,
"\t\t],\n"
- "\t\t\"manifests\": %zu,\n"
- "\t\t\"failedmanifests\": %zu,\n"
- "\t\t\"stalemanifests\": %zu,\n"
- "\t\t\"crls\": %zu,\n"
- "\t\t\"gbrs\": %zu,\n"
- "\t\t\"repositories\": %zu,\n"
- "\t\t\"vrps\": %zu,\n"
- "\t\t\"uniquevrps\": %zu,\n"
- "\t\t\"vaps\": %zu,\n"
- "\t\t\"uniquevaps\": %zu,\n"
- "\t\t\"cachedir_del_files\": %zu,\n"
- "\t\t\"cachedir_superfluous_files\": %zu,\n"
- "\t\t\"cachedir_del_dirs\": %zu\n"
+ "\t\t\"manifests\": %u,\n"
+ "\t\t\"failedmanifests\": %u,\n"
+ "\t\t\"stalemanifests\": %u,\n"
+ "\t\t\"crls\": %u,\n"
+ "\t\t\"gbrs\": %u,\n"
+ "\t\t\"repositories\": %u,\n"
+ "\t\t\"vrps\": %u,\n"
+ "\t\t\"uniquevrps\": %u,\n"
+ "\t\t\"vaps\": %u,\n"
+ "\t\t\"uniquevaps\": %u,\n"
+ "\t\t\"cachedir_del_files\": %u,\n"
+ "\t\t\"cachedir_superfluous_files\": %u,\n"
+ "\t\t\"cachedir_del_dirs\": %u\n"
"\t},\n\n",
- st->mfts, st->mfts_fail, st->mfts_stale,
- st->crls,
- st->gbrs,
+ st->repo_stats.mfts,
+ st->repo_stats.mfts_fail,
+ st->repo_stats.mfts_stale,
+ st->repo_stats.crls,
+ st->repo_stats.gbrs,
st->repos,
- st->vrps, st->uniqs,
- st->vaps, st->vaps_uniqs,
- st->del_files, st->extra_files, st->del_dirs) < 0)
+ st->repo_stats.vrps,
+ st->repo_stats.vrps_uniqs,
+ st->repo_stats.vaps,
+ st->repo_stats.vaps_uniqs,
+ st->del_files,
+ st->extra_files,
+ st->del_dirs) < 0)
return -1;
return 0;
}
Index: output-ometric.c
===================================================================
RCS file: output-ometric.c
diff -N output-ometric.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ output-ometric.c 14 Dec 2022 17:16:42 -0000
@@ -0,0 +1,212 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2022 Claudio Jeker <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+#include "ometric.h"
+#include "version.h"
+
+struct ometric *rpki_info, *rpki_completion_time, *rpki_duration;
+struct ometric *rpki_repo, *rpki_obj, *rpki_ta_obj;
+struct ometric *rpki_repo_obj, *rpki_repo_duration, *rpki_repo_state;
+
+static const char * const repo_states[2] = { "failed", "synced" };
+
+static void
+set_common_stats(const struct repostats *in, struct ometric *metric,
+ struct olabels *ol)
+{
+ ometric_set_int_with_labels(metric, in->certs,
+ OKV("type", "state"), OKV("cert", "valid"), ol);
+ ometric_set_int_with_labels(metric, in->certs_fail,
+ OKV("type", "state"), OKV("cert", "failed parse"), ol);
+
+ ometric_set_int_with_labels(metric, in->mfts,
+ OKV("type", "state"), OKV("manifest", "valid"), ol);
+ ometric_set_int_with_labels(metric, in->mfts_fail,
+ OKV("type", "state"), OKV("manifest", "failed parse"), ol);
+ ometric_set_int_with_labels(metric, in->mfts_stale,
+ OKV("type", "state"), OKV("manifest", "stale"), ol);
+
+ ometric_set_int_with_labels(metric, in->roas,
+ OKV("type", "state"), OKV("roa", "valid"), ol);
+ ometric_set_int_with_labels(metric, in->roas_fail,
+ OKV("type", "state"), OKV("roa", "failed parse"), ol);
+ ometric_set_int_with_labels(metric, in->roas_invalid,
+ OKV("type", "state"), OKV("roa", "invalid"), ol);
+
+ ometric_set_int_with_labels(metric, in->aspas,
+ OKV("type", "state"), OKV("aspa", "valid"), ol);
+ ometric_set_int_with_labels(metric, in->aspas_fail,
+ OKV("type", "state"), OKV("aspa", "failed parse"), ol);
+ ometric_set_int_with_labels(metric, in->aspas_invalid,
+ OKV("type", "state"), OKV("aspa", "invalid"), ol);
+
+ ometric_set_int_with_labels(metric, in->brks,
+ OKV("type", "state"), OKV("router_key", "valid"), ol);
+ ometric_set_int_with_labels(metric, in->crls,
+ OKV("type", "state"), OKV("crl", "valid"), ol);
+ ometric_set_int_with_labels(metric, in->gbrs,
+ OKV("type", "state"), OKV("gbr", "valid"), ol);
+ ometric_set_int_with_labels(metric, in->taks,
+ OKV("type", "state"), OKV("tak", "valid"), ol);
+
+ ometric_set_int_with_labels(metric, in->vrps,
+ OKV("type", "state"), OKV("vrp", "total"), ol);
+ ometric_set_int_with_labels(metric, in->vrps_uniqs,
+ OKV("type", "state"), OKV("vrp", "unique"), ol);
+
+ ometric_set_int_with_labels(metric, in->vaps,
+ OKV("type", "state"), OKV("vap", "total"), ol);
+ ometric_set_int_with_labels(metric, in->vaps_uniqs,
+ OKV("type", "state"), OKV("vap", "unique"), ol);
+ ometric_set_int_with_labels(metric, in->vaps_pas,
+ OKV("type", "state"), OKV("vap providers", "both"), ol);
+ ometric_set_int_with_labels(metric, in->vaps_pas4,
+ OKV("type", "state"), OKV("vap providers", "IPv4 only"), ol);
+ ometric_set_int_with_labels(metric, in->vaps_pas6,
+ OKV("type", "state"), OKV("vap providers", "IPv6 only"), ol);
+}
+
+static void
+ta_stats(int id)
+{
+ struct olabels *ol;
+ const char *keys[2] = { "name", NULL };
+ const char *values[2];
+
+ values[0] = taldescs[id];
+ values[1] = NULL;
+
+ ol = olabels_new(keys, values);
+ set_common_stats(&talstats[id], rpki_ta_obj, ol);
+ olabels_free(ol);
+}
+
+static void
+repo_stats(const struct repo *rp, const struct repostats *in, void *arg)
+{
+ struct olabels *ol;
+ const char *keys[4] = { "name", "carepo", "notify", NULL };
+ const char *values[4];
+
+ values[0] = taldescs[repo_talid(rp)];
+ repo_fetch_uris(rp, &values[1], &values[2]);
+ values[3] = NULL;
+
+ ol = olabels_new(keys, values);
+ set_common_stats(in, rpki_repo_obj, ol);
+ ometric_set_timespec(rpki_repo_duration, &in->sync_time, ol);
+ ometric_set_state(rpki_repo_state, repo_states[repo_synced(rp)], ol);
+ olabels_free(ol);
+}
+
+int
+output_ometric(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+ struct vap_tree *vaps, struct stats *st)
+{
+ struct olabels *ol;
+ const char *keys[4] = { "nodename", "domainname", "release", NULL };
+ const char *values[4];
+ char hostname[HOST_NAME_MAX + 1];
+ char *domainname;
+ struct timespec now_time;
+ int rv, i;
+
+ rpki_info = ometric_new(OMT_INFO, "rpki_client",
+ "rpki-client information");
+ rpki_completion_time = ometric_new(OMT_GAUGE,
+ "rpki_client_job_completion_time",
+ "end of this run as epoch timestamp");
+
+ rpki_repo = ometric_new(OMT_GAUGE, "rpki_client_repository",
+ "total number of repositories");
+ rpki_obj = ometric_new(OMT_GAUGE, "rpki_client_objects",
+ "total number of objects");
+
+ rpki_duration = ometric_new(OMT_GAUGE, "rpki_client_duration",
+ "duration in seconds");
+
+ rpki_ta_obj = ometric_new(OMT_GAUGE, "rpki_client_ta_objects",
+ "total number of objects per TAL");
+ rpki_repo_obj = ometric_new(OMT_GAUGE, "rpki_client_repository_objects",
+ "total number of objects per repository");
+ rpki_repo_duration = ometric_new(OMT_GAUGE,
+ "rpki_client_repository_duration",
+ "duration used to sync this repository in seconds");
+ rpki_repo_state = ometric_new_state(repo_states,
+ sizeof(repo_states) / sizeof(repo_states[0]),
+ "rpki_client_repository_state",
+ "repository state");
+
+ /*
+ * Dump statistics
+ */
+ if (gethostname(hostname, sizeof(hostname)))
+ err(1, "gethostname");
+ if ((domainname = strchr(hostname, '.')))
+ *domainname++ = '\0';
+
+ values[0] = hostname;
+ values[1] = domainname;
+ values[2] = RPKI_VERSION;
+ values[3] = NULL;
+
+ ol = olabels_new(keys, values);
+ ometric_set_info(rpki_info, NULL, NULL, ol);
+ olabels_free(ol);
+
+ repo_stats_collect(repo_stats, NULL);
+ for (i = 0; i < talsz; i++)
+ ta_stats(i);
+ set_common_stats(&st->repo_stats, rpki_obj, NULL);
+
+ ometric_set_int(rpki_repo, st->repos, NULL);
+ ometric_set_int_with_labels(rpki_repo, st->rsync_repos,
+ OKV("type", "state"), OKV("rsync", "synced"), NULL);
+ ometric_set_int_with_labels(rpki_repo, st->rsync_fails,
+ OKV("type", "state"), OKV("rsync", "failed"), NULL);
+ ometric_set_int_with_labels(rpki_repo, st->http_repos,
+ OKV("type", "state"), OKV("http", "synced"), NULL);
+ ometric_set_int_with_labels(rpki_repo, st->http_fails,
+ OKV("type", "state"), OKV("http", "failed"), NULL);
+ ometric_set_int_with_labels(rpki_repo, st->rrdp_repos,
+ OKV("type", "state"), OKV("rrdp", "synced"), NULL);
+ ometric_set_int_with_labels(rpki_repo, st->rrdp_fails,
+ OKV("type", "state"), OKV("rrdp", "failed"), NULL);
+
+ ometric_set_timespec_with_labels(rpki_duration, &st->elapsed_time,
+ OKV("type"), OKV("elapsed"), NULL);
+ ometric_set_timespec_with_labels(rpki_duration, &st->user_time,
+ OKV("type"), OKV("user"), NULL);
+ ometric_set_timespec_with_labels(rpki_duration, &st->system_time,
+ OKV("type"), OKV("system"), NULL);
+
+ clock_gettime(CLOCK_REALTIME, &now_time);
+ ometric_set_timespec(rpki_completion_time, &now_time, NULL);
+
+ rv = ometric_output_all(out);
+ ometric_free_all();
+
+ return rv;
+}
Index: output.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output.c,v
retrieving revision 1.28
diff -u -p -r1.28 output.c
--- output.c 4 Nov 2022 13:01:19 -0000 1.28
+++ output.c 1 Dec 2022 17:27:55 -0000
@@ -73,6 +73,7 @@ static const struct outputs {
{ FORMAT_BIRD, "bird", output_bird2 },
{ FORMAT_CSV, "csv", output_csv },
{ FORMAT_JSON, "json", output_json },
+ { FORMAT_OMETRIC, "metrics", output_ometric },
{ 0, NULL, NULL }
};
@@ -213,17 +214,18 @@ outputheader(FILE *out, struct stats *st
if (fprintf(out,
"# Generated on host %s at %s\n"
"# Processing time %lld seconds (%llds user, %llds system)\n"
- "# Route Origin Authorizations: %zu (%zu failed parse, %zu
invalid)\n"
- "# BGPsec Router Certificates: %zu\n"
- "# Certificates: %zu (%zu invalid)\n",
+ "# Route Origin Authorizations: %u (%u failed parse, %u invalid)\n"
+ "# BGPsec Router Certificates: %u\n"
+ "# Certificates: %u (%u invalid)\n",
hn, tbuf, (long long)st->elapsed_time.tv_sec,
(long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
- st->roas, st->roas_fail, st->roas_invalid,
- st->brks, st->certs, st->certs_fail) < 0)
+ st->repo_stats.roas, st->repo_stats.roas_fail,
+ st->repo_stats.roas_invalid, st->repo_stats.brks,
+ st->repo_stats.certs, st->repo_stats.certs_fail) < 0)
return -1;
if (fprintf(out,
- "# Trust Anchor Locators: %zu (%zu invalid) [", st->tals,
+ "# Trust Anchor Locators: %u (%u invalid) [", st->tals,
talsz - st->tals) < 0)
return -1;
for (i = 0; i < talsz; i++)
@@ -232,16 +234,17 @@ outputheader(FILE *out, struct stats *st
if (fprintf(out,
" ]\n"
- "# Manifests: %zu (%zu failed parse, %zu stale)\n"
- "# Certificate revocation lists: %zu\n"
- "# Ghostbuster records: %zu\n"
- "# Repositories: %zu\n"
- "# VRP Entries: %zu (%zu unique)\n",
- st->mfts, st->mfts_fail, st->mfts_stale,
- st->crls,
- st->gbrs,
+ "# Manifests: %u (%u failed parse, %u stale)\n"
+ "# Certificate revocation lists: %u\n"
+ "# Ghostbuster records: %u\n"
+ "# Repositories: %u\n"
+ "# VRP Entries: %u (%u unique)\n",
+ st->repo_stats.mfts, st->repo_stats.mfts_fail,
+ st->repo_stats.mfts_stale,
+ st->repo_stats.crls,
+ st->repo_stats.gbrs,
st->repos,
- st->vrps, st->uniqs) < 0)
+ st->repo_stats.vrps, st->repo_stats.vrps_uniqs) < 0)
return -1;
return 0;
}
Index: parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
retrieving revision 1.80
diff -u -p -r1.80 parser.c
--- parser.c 29 Nov 2022 20:26:22 -0000 1.80
+++ parser.c 30 Nov 2022 09:03:46 -0000
@@ -636,6 +636,8 @@ parse_entity(struct entityq *q, struct m
switch (entp->type) {
case RTYPE_TAL:
io_str_buffer(b, entp->file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
if ((tal = tal_parse(entp->file, entp->data,
entp->datasz)) == NULL)
errx(1, "%s: could not parse tal file",
@@ -647,6 +649,8 @@ parse_entity(struct entityq *q, struct m
case RTYPE_CER:
file = parse_load_file(entp, &f, &flen);
io_str_buffer(b, file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
if (entp->data != NULL)
cert = proc_parser_root_cert(file,
f, flen, entp->data, entp->datasz,
@@ -673,10 +677,14 @@ parse_entity(struct entityq *q, struct m
file = parse_filepath(entp->repoid, entp->path,
entp->file, entp->location);
io_str_buffer(b, file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
break;
case RTYPE_MFT:
file = proc_parser_mft(entp, &mft);
io_str_buffer(b, file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
c = (mft != NULL);
io_simple_buffer(b, &c, sizeof(int));
if (mft != NULL)
@@ -686,6 +694,8 @@ parse_entity(struct entityq *q, struct m
case RTYPE_ROA:
file = parse_load_file(entp, &f, &flen);
io_str_buffer(b, file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
roa = proc_parser_roa(file, f, flen);
c = (roa != NULL);
io_simple_buffer(b, &c, sizeof(int));
@@ -696,11 +706,15 @@ parse_entity(struct entityq *q, struct m
case RTYPE_GBR:
file = parse_load_file(entp, &f, &flen);
io_str_buffer(b, file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
proc_parser_gbr(file, f, flen);
break;
case RTYPE_ASPA:
file = parse_load_file(entp, &f, &flen);
io_str_buffer(b, file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
aspa = proc_parser_aspa(file, f, flen);
c = (aspa != NULL);
io_simple_buffer(b, &c, sizeof(int));
@@ -711,6 +725,8 @@ parse_entity(struct entityq *q, struct m
case RTYPE_TAK:
file = parse_load_file(entp, &f, &flen);
io_str_buffer(b, file);
+ io_simple_buffer(b, &entp->repoid,
+ sizeof(entp->repoid));
proc_parser_tak(file, f, flen);
break;
default:
Index: repo.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/repo.c,v
retrieving revision 1.39
diff -u -p -r1.39 repo.c
--- repo.c 2 Sep 2022 21:56:45 -0000 1.39
+++ repo.c 14 Dec 2022 17:27:57 -0000
@@ -97,6 +97,8 @@ struct repo {
const struct rsyncrepo *rsync;
const struct tarepo *ta;
struct entityq queue; /* files waiting for repo */
+ struct repostats stats;
+ struct timespec start_time;
time_t alarm; /* sync timeout */
int talid;
unsigned int id; /* identifier */
@@ -263,7 +265,7 @@ repo_mkpath(int fd, char *file)
* Return the state of a repository.
*/
static enum repo_state
-repo_state(struct repo *rp)
+repo_state(const struct repo *rp)
{
if (rp->ta)
return rp->ta->state;
@@ -283,24 +285,24 @@ static void
repo_done(const void *vp, int ok)
{
struct repo *rp;
+ struct timespec flush_time;
SLIST_FOREACH(rp, &repos, entry) {
- if (vp == rp->ta)
- entityq_flush(&rp->queue, rp);
- if (vp == rp->rsync)
- entityq_flush(&rp->queue, rp);
- if (vp == rp->rrdp) {
- if (!ok && !nofetch) {
- /* try to fall back to rsync */
- rp->rrdp = NULL;
- rp->rsync = rsync_get(rp->repouri,
- rp->basedir);
- /* need to check if it was already loaded */
- if (repo_state(rp) != REPO_LOADING)
- entityq_flush(&rp->queue, rp);
- } else
- entityq_flush(&rp->queue, rp);
+ if (vp != rp->ta && vp != rp->rsync && vp != rp->rrdp)
+ continue;
+
+ /* for rrdp try to fall back to rsync */
+ if (vp == rp->rrdp && !ok && !nofetch) {
+ rp->rrdp = NULL;
+ rp->rsync = rsync_get(rp->repouri, rp->basedir);
+ /* need to check if it was already loaded */
+ if (repo_state(rp) == REPO_LOADING)
+ continue;
}
+
+ entityq_flush(&rp->queue, rp);
+ clock_gettime(CLOCK_MONOTONIC, &flush_time);
+ timespecsub(&flush_time, &rp->start_time, &rp->stats.sync_time);
}
}
@@ -600,6 +602,7 @@ repo_alloc(int talid)
rp->alarm = getmonotime() + repo_timeout;
TAILQ_INIT(&rp->queue);
SLIST_INSERT_HEAD(&repos, rp, entry);
+ clock_gettime(CLOCK_MONOTONIC, &rp->start_time);
stats.repos++;
return rp;
@@ -1179,6 +1182,38 @@ repo_uri(const struct repo *rp)
return rp->repouri;
}
+/*
+ * Return the repository URI.
+ */
+void
+repo_fetch_uris(const struct repo *rp, const char **carepo,
+ const char **notifyuri)
+{
+ *carepo = rp->repouri;
+ *notifyuri = rp->notifyuri;
+}
+
+/*
+ * Return 1 if repository is synced else 0.
+ */
+int
+repo_synced(const struct repo *rp)
+{
+ if (repo_state(rp) == REPO_DONE &&
+ !(rp->rrdp == NULL && rp->rsync == NULL && rp->ta == NULL))
+ return 1;
+ return 0;
+}
+
+/*
+ * Return the repository tal ID.
+ */
+int
+repo_talid(const struct repo *rp)
+{
+ return rp->talid;
+}
+
int
repo_queued(struct repo *rp, struct entity *p)
{
@@ -1260,6 +1295,112 @@ repo_check_timeout(int timeout)
}
}
return timeout;
+}
+
+/*
+ * Update stats object of repository depending on rtype and subtype.
+ */
+void
+repo_stat_inc(struct repo *rp, enum rtype type, enum stype subtype)
+{
+ if (rp == NULL)
+ return;
+ switch (type) {
+ case RTYPE_CER:
+ if (subtype == STYPE_OK)
+ rp->stats.certs++;
+ if (subtype == STYPE_FAIL)
+ rp->stats.certs_fail++;
+ if (subtype == STYPE_BGPSEC) {
+ rp->stats.certs--;
+ rp->stats.brks++;
+ }
+ break;
+ case RTYPE_MFT:
+ if (subtype == STYPE_OK)
+ rp->stats.mfts++;
+ if (subtype == STYPE_FAIL)
+ rp->stats.mfts_fail++;
+ if (subtype == STYPE_STALE)
+ rp->stats.mfts_stale++;
+ break;
+ case RTYPE_ROA:
+ switch (subtype) {
+ case STYPE_OK:
+ rp->stats.roas++;
+ break;
+ case STYPE_FAIL:
+ rp->stats.roas_fail++;
+ break;
+ case STYPE_INVALID:
+ rp->stats.roas_invalid++;
+ break;
+ case STYPE_TOTAL:
+ rp->stats.vrps++;
+ break;
+ case STYPE_UNIQUE:
+ rp->stats.vrps_uniqs++;
+ break;
+ case STYPE_DEC_UNIQUE:
+ rp->stats.vrps_uniqs--;
+ break;
+ default:
+ break;
+ }
+ break;
+ case RTYPE_ASPA:
+ switch (subtype) {
+ case STYPE_OK:
+ rp->stats.aspas++;
+ break;
+ case STYPE_FAIL:
+ rp->stats.aspas_fail++;
+ break;
+ case STYPE_INVALID:
+ rp->stats.aspas_invalid++;
+ break;
+ case STYPE_TOTAL:
+ rp->stats.vaps++;
+ break;
+ case STYPE_UNIQUE:
+ rp->stats.vaps_uniqs++;
+ break;
+ case STYPE_BOTH:
+ rp->stats.vaps_pas++;
+ break;
+ case STYPE_ONLY_IPV4:
+ rp->stats.vaps_pas4++;
+ break;
+ case STYPE_ONLY_IPV6:
+ rp->stats.vaps_pas6++;
+ break;
+ default:
+ break;
+ }
+ break;
+ case RTYPE_CRL:
+ rp->stats.crls++;
+ break;
+ case RTYPE_GBR:
+ rp->stats.gbrs++;
+ break;
+ case RTYPE_TAK:
+ rp->stats.taks++;
+ break;
+ default:
+ break;
+ }
+}
+
+void
+repo_stats_collect(void (*cb)(const struct repo *, const struct repostats *,
+ void *), void *arg)
+{
+ struct repo *rp;
+
+ SLIST_FOREACH(rp, &repos, entry) {
+ cb(rp, &rp->stats, arg);
+ }
}
/*
Index: roa.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/roa.c,v
retrieving revision 1.58
diff -u -p -r1.58 roa.c
--- roa.c 29 Nov 2022 20:41:32 -0000 1.58
+++ roa.c 14 Dec 2022 17:29:23 -0000
@@ -360,8 +360,7 @@ roa_read(struct ibuf *b)
* number of addresses.
*/
void
-roa_insert_vrps(struct vrp_tree *tree, struct roa *roa, size_t *vrps,
- size_t *uniqs)
+roa_insert_vrps(struct vrp_tree *tree, struct roa *roa, struct repo *rp)
{
struct vrp *v, *found;
size_t i;
@@ -374,6 +373,10 @@ roa_insert_vrps(struct vrp_tree *tree, s
v->maxlength = roa->ips[i].maxlength;
v->asid = roa->asid;
v->talid = roa->talid;
+ if (rp != NULL)
+ v->repoid = repo_id(rp);
+ else
+ v->repoid = 0;
v->expires = roa->expires;
/*
@@ -387,12 +390,17 @@ roa_insert_vrps(struct vrp_tree *tree, s
/* update found with preferred data */
found->talid = v->talid;
found->expires = v->expires;
+ /* adjust unique count */
+ repo_stat_inc(repo_byid(found->repoid),
+ RTYPE_ROA, STYPE_DEC_UNIQUE);
+ found->repoid = v->repoid;
+ repo_stat_inc(rp, RTYPE_ROA, STYPE_UNIQUE);
}
free(v);
} else
- (*uniqs)++;
+ repo_stat_inc(rp, RTYPE_ROA, STYPE_UNIQUE);
- (*vrps)++;
+ repo_stat_inc(rp, RTYPE_ROA, STYPE_TOTAL);
}
}
Index: rpki-client.8
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v
retrieving revision 1.81
diff -u -p -r1.81 rpki-client.8
--- rpki-client.8 26 Nov 2022 12:02:37 -0000 1.81
+++ rpki-client.8 14 Dec 2022 09:19:30 -0000
@@ -22,7 +22,7 @@
.Nd RPKI validator to support BGP routing security
.Sh SYNOPSIS
.Nm
-.Op Fl BcjnoRrVv
+.Op Fl BcjmnoRrVv
.Op Fl b Ar sourceaddr
.Op Fl d Ar cachedir
.Op Fl e Ar rsync_prog
@@ -138,6 +138,10 @@ in the output directory as JSON object.
See
.Fl c
for a description of the fields.
+.It Fl m
+Create output in the file
+.Pa metrics
+in the output directory in OpenMetrics format.
.It Fl n
Offline mode.
Validate the contents of