Skip to content

Expose opt-in Mako Prometheus metrics - #14040

Open
mengxu-oai wants to merge 2 commits into
apple:mainfrom
mengxu-oai:dev/mengxu/mako-prometheus-main
Open

mengxu-oai wants to merge 2 commits into
apple:mainfrom
mengxu-oai:dev/mengxu/mako-prometheus-main

Conversation

@mengxu-oai

@mengxu-oai mengxu-oai commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Mako currently reports live throughput to stdout and sampled latency only through process-private data, so a workload scraper cannot read client-side throughput and latency during a run. Add opt-in --prometheus_port=PORT (disabled by default) to serve /metrics from Mako's stats process. Per-worker shared-memory atomics expose completed and attempted transactions, GET/UPDATE client API steps, commits, conflicts, errors, timeouts, and estimated logical bytes. A bounded cumulative histogram exports successful sampled GET and COMMIT latency in seconds for Prometheus rate and histogram_quantile. The endpoint also reports configured workers, sampling, and job/workload labels; stdout interval latency JSON is removed.

The exporter allocates shared histogram/counter storage only when enabled. Roughly 250 finite latency buckets per operation provide about 4.4% width from 100 µs through 1 s; production dashboards should scope by pod and account for this series count.

Validation: focused CTest covers cross-process aggregation, cumulative histogram bounds, HTTP 200/404, and occupied-port failure; changed Mako and async translation units compile, clang-format dry-run and whitespace checks pass.

@mengxu-oai
mengxu-oai marked this pull request as draft September 11, 2026 21:06

@saintstack saintstack left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mako is distinct but was wondering why not use existing fdb otel or statsd? Why add 3rd model, and one that is pull based rather than push (is pull good for mako stats when runs are short).

We also include a minimal http server here. You might make the argument for it (I think its easy -- smile) otherwise folks will wonder why codebase has a second one apart from fdbrpc/HTTPServer.cpp.

Thanks.

}
switch (c) {
case '?':
return -2;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should print help msg by usage(); before return -2.

@mengxu-oai

Copy link
Copy Markdown
Contributor Author

mako is distinct but was wondering why not use existing fdb otel or statsd? Why add 3rd model, and one that is pull based rather than push (is pull good for mako stats when runs are short).

We also include a minimal http server here. You might make the argument for it (I think its easy -- smile) otherwise folks will wonder why codebase has a second one apart from fdbrpc/HTTPServer.cpp.

Thanks.

Hi @saintstack ,
Thank you very much for reviewing this PR quickly!

The problem we want to solve is to run a distributed Mako workload against an FDB cluster to do the load testing and release performance testing. A single box Mako workload is not enough to drive enough workload against a FDB cluster, so we need multiple mako instances (on k8s) driving load against a FDB cluster.

We want to measure the client-side performance, including P50 and P99 latency of GET and update requests and the QPS, IOPS throughput metrics. That's why we want to expose the metrics from each micro instance and leverage Grafana to create the stats and client-side performance dashboard.

regarding FDB StatsD and OTEL, reasons are:

  1. I think these are for fdbservers. mako is an independent workload/binary that do not rely on FDB's flow runtime.
  2. Grafana default model is pull instead of push model.
  3. As you mentioned, it's simplier, and more importantly cleaner (keeping mako and fdbserver separate), to create a server.

I'm open to suggestions.

@saintstack saintstack left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Will review when you move it out of draft mode.

Thanks.

@mengxu-oai
mengxu-oai marked this pull request as ready for review September 12, 2026 16:05
@mengxu-oai

Copy link
Copy Markdown
Contributor Author

LGTM

Will review when you move it out of draft mode.

Thanks.

Thank you! @saintstack i marked it ready.

@saintstack

saintstack commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Was looking at the http server again. No keep-alive, no chunking, no pipelining but I think its fine. One suggestion given you have it is to use more of boost beast: you can delete request.rfind("GET /metrics HTTP/1.", 0) == 0 and you get stuff like absolute-form URI, and leading-CRLF tolerance for the same LoC. beast::tcp_stream::expires_after gives you the read timeout in one line.

Suggestions from my 'friend' given you are doing the raw asio are:

  • no read/idle deadline — a connection that opens and sends nothing pins a Session forever; no cap on concurrent connections either. That's the one real bug.
  • IPv4-only (Tcp::v4(), wildcard bind). On an IPv6 k8s cluster the scrape just fails. Add --prometheus_addr.
  • Session::render is a std::function const& into the parent; it survives only because of member declaration order. Copy it or hold a shared_ptr.
  • constructor calls accept() and starts the thread before construction completes; if std::thread throws you destroy an io_context with a pending handler and never run the dtor.

I was asking my friend about the standalone, hand-written http server and it was more concerned about prometheus.hpp + native_latency.hpp with hand-rolled format and a 250-bucket cumulative histogram with hand-strided index math (for (int index = 426; index <= 1278; index += 4)).

I looked at having mako just dump a file and let something like a telegraf host it but more work.

Sorry for all the comments. Am interested in this project. Would be good to have something like it back in core fdb on each process: e.g. different context completely but just FYI #12680.

@saintstack

Copy link
Copy Markdown
Contributor

From local LLM. See what you think:

  • OP_TRANSACTION latency is not exported. It is already computed and passed to addLatency (mako.cpp:363, async.cpp:206), and end-to-end transaction latency is the single most useful number for a workload scraper. native_latency.hpp:384 operationCount = 2 excludes it for no stated reason. Bumping to 3 costs ~32 KB per worker.
  • Session::render is a dangling-reference hazard. prometheus_server.hpp:890 holds std::functionstd::string() const& to the server's member. Member destruction order is thread, render, acceptor, io — so io's destructor destroys abandoned handlers (and the last Session) after render is already gone. Benign today only because ~Session never calls render. Store it by value or as shared_ptr; one std::function copy per scrape is free.
  • No read/write timeout or connection cap on the exporter. A client that connects and never sends a full header holds a Session until process exit. Bound on 0.0.0.0 with no auth. Opt-in mitigates this, but a steady_timer on the read is ~5 lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants