I am studying how to use Prometheus in multiprocessing python code (without Gunicorn).
For now i came to following simple example with two child processes:
frommultiprocessingimportProcessimportshutilimporttime, osos.environ["PROMETHEUS_MULTIPROC_DIR"] ='./PMDir'# This environment variable must be set BEFORE the first import # from prometheus_client to ensure the library uses MultiProcessValue # (metric value class with mmap support). Otherwise, # MutexValue (non-mmap) will be used, and files won't be created.fromprometheus_clientimportstart_http_server, multiprocess, CollectorRegistry, Counteros.environ["PROMETHEUS_MULTIPROC_DIR"] ='./PMDir'# If we set it AFTER import, the library initializes # the value class = MutexValue, mmap files will not be created,# and MultiProcessCollector will have nothing to work with.# This matches the documentation's recommendation to set it # before app startup.COUNTER1=NoneCOUNTER2=NoneCOUNTER3=NoneCOUNTER4=Nonedefinit_counters(registry):
""" Initialize counters (or other metrics) and register them in the specified registry. According to MetricWrapperBase's code, if registry=None, metrics won't be registered in any registry. """globalCOUNTER1, COUNTER2, COUNTER3, COUNTER4COUNTER1=Counter('counter1', 'Incremented by the first child process', registry=registry)
COUNTER2=Counter('counter2', 'Incremented by the second child process', registry=registry)
COUNTER3=Counter('counter3', 'Incremented by all processes', registry=registry)
COUNTER4=Counter('counter4', 'Incremented by main process', registry=registry)
# We are free not to create registry object in child processes. Both f1 and f2 works as process targets.# The mmap file handling is managed at the metric object level, not the collector level.# Variation 1: create registry or not to create registry - both works as I expect.deff1():
"""First child process body. Works without manual registry creation."""init_counters(None)
whileTrue:
time.sleep(1)
print("Child process 1", os.getpid())
COUNTER1.inc()
COUNTER3.inc()
deff2():
"""Second child process body. Works with manual registry creation."""registry=CollectorRegistry()
init_counters(registry)
whileTrue:
time.sleep(2)
print("Child process 2", os.getpid())
COUNTER2.inc()
COUNTER3.inc()
if__name__=='__main__':
# Ensure the multiprocess directory exists and is emptyprome_stats=os.environ["PROMETHEUS_MULTIPROC_DIR"]
ifos.path.exists(prome_stats):
shutil.rmtree(prome_stats)
os.mkdir(prome_stats)
# Variation 2: When using MultiProcessCollector directly (see Variation 4), registry creation is optional# registry = CollectorRegistry() # Create registry for HTTP serverregistry=None# Works without registry between mpc and http server# Create MultiProcessCollector object. It reads and aggregates mmap files from PROMETHEUS_MULTIPROC_DIR.# Registering it in our registry means thar registry.collect() calls mpc.collect() and thus metrics from # mpc aggregated by registry.# MultiProcessCollector ONLY reads (mmap) files; metric saving is handled by MultiProcessValue in metrics obj.mpc=multiprocess.MultiProcessCollector(registry)
# Variation 3: If main process have to report its own metrics it can use both separate CollectorRegistry or None.# init_counters(CollectorRegistry()) # Use separate registry for main process metrics.init_counters(None) # Works without registry registration# init_counters(registry) # Metrics will duplicate if we use same registry as that one mpc registered in.# # More precisely, in this situation registry will export both mpc metrics and # # main process metrics, including overlaps# Variation 4: HTTP server can use both registry or mpc# start_http_server(8000, registry=registry) # Standard approachstart_http_server(8000, registry=mpc) # Works despite mpc not being a Collector subclass (but implements collect())p1=Process(target=f1, args=())
p1.start()
p2=Process(target=f2, args=())
p2.start()
print("collect")
try:
whileTrue:
print('main process ', os.getpid())
time.sleep(1)
COUNTER3.inc()
COUNTER4.inc()
exceptKeyboardInterrupt:
p1.terminate()
p2.terminate()
shutil.rmtree(prome_stats)(I left my findings as comments because documentation didnt answer my questions and I can make mistakes in my assumptions)
I noticed that MultiProcessCollector implements collect() but doesn't inherit from the Collector class.
The Custom collectors documentation suggests that custom collectors should implement this interface. However, MultiProcessCollector is defined as:
classMultiProcessCollector:
"""Collector for files for multi-process mode."""def__init__(self, registry, path=None):
...
This seems contradictory since:
- The class name contains "Collector"
- It implements the core
collect() method - It's designed to be registered in a
CollectorRegistry
Could you clarify:
- Is this intentional design? If yes, what's the rationale behind not inheriting from
Collector? - Are there any potential compatibility risks in not following the
Collector interface contract? - What are potential risks of using
MultiProcessCollector object in start_http_server directly, without registry = CollectorRegistry() between them?
I want to ensure I understand this correctly for proper integration. Thanks for your insights!
I am studying how to use Prometheus in multiprocessing python code (without Gunicorn).
For now i came to following simple example with two child processes:
(I left my findings as comments because documentation didnt answer my questions and I can make mistakes in my assumptions)
I noticed that
MultiProcessCollectorimplementscollect()but doesn't inherit from theCollectorclass.The Custom collectors documentation suggests that custom collectors should implement this interface. However,
MultiProcessCollectoris defined as:This seems contradictory since:
collect()methodCollectorRegistryCould you clarify:
Collector?Collectorinterface contract?MultiProcessCollectorobject instart_http_serverdirectly, withoutregistry = CollectorRegistry()between them?I want to ensure I understand this correctly for proper integration. Thanks for your insights!