-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Integration of Prometheus Push Gateway and Energy Metrics Collection in Zeus #125
Comments
Thanks @sharonsyh for the awesome write-up!! This is going to be a great addition to Zeus.
|
1. It's not exactly clear to me what CollectorRegistry is for. What is the role of this class? Is this part of the Prometheus client library?CollectorRegistry is part of the Prometheus client library which acts as the collection hub for all metrics (Histogram, Counter, Gauge). Every metric is registered to a registry that tracks the metric values before pushing them to the Prometheus Push Gateway. It is not necessary to use CollectorRegistry for us since we only have one registry and the default registry automatically tracks all metrics we define (like Histograms, Gauges, and Counters) without the need for you to manually pass a custom registry. However, to have a full control over what metrics are pushed and to separate metrics into different groups or namespaces, customizing the registry (using CollectorRegistry) seems to be a better way. 2. Similarly, is the Histogram class part of the Python Prometheus client library? If so, I want you to clearly distinguish what is going to be built as part of Zeus and what is going to be imported from external libraries.The Histogram class is also part of the Prometheus client library. It is used to create and record histogram data, such as total energy consumption, with the capability to track consumption across different buckets.
3. I think there should be a way for users to specify the endpoint of the Prometheus Push Gateway when defining metrics.I agree it’s a good idea to allow the user to specify the Push Gateway endpoint at the time of defining the metrics. I assume it could be implemented with the code below: metric = HistogramMetric(monitor=monitor, bucket_ranges=[50, 100, 200], prometheus_url=’SPECIFY THE ENDPOINT’) 4. Since you're designing the histogram and counter metrics, can you also do it for gauge? It's not likely but you may find that the gauge metric is not exactly compatible with some parts of the current design (e.g., histogram, counter), in which case it's better to adjust the design early on before we do any work. I presume it will be very similar to CounterMetric.Yes, I have a plan to work on the structure for Gauge soon. I'll update to this write-up once I get it done! 5. I would use update_period instead of poll_period in CounterMetric.Got it! 6. I think using the default bucket range should also result in a warning, since it's likely that the default range will either be too small or too large. I think it's very likely that GPU, CPU, and DRAM energy values have vastly different ranges. Thus, I think bucket ranges should be separately configurable and should have different defaults. Do different labels allow different buckets?We can't have different bucket ranges for each component as each Histogram metric applies its bucket ranges globally across all labels for that particular histogram. If we are to use different default bucket ranges, I assume we have to define Histogram metrics seperately for each component with its respective bucket ranges. Since it's very likely that users might only use one of GPU, CPU, or DRAM, creating separate histograms for each component with appropriate bucket ranges would allow flexibility while preventing issues with bucket ranges that might be too broad or too narrow. # Previously
self.energy_histogram = Histogram(
'total_energy',
'Total energy consumed',
['component', 'window'],
buckets=bucket_ranges,
registry=registry
)
# Possible solution
# Seperating the Histogram metrics by each component
# Each default_gpu_buckets, default_cpu_buckets, default_dram_buckets will be defined separately
def __init__(self, monitor, gpu_buckets=None, cpu_buckets=None, dram_buckets=None):
self.gpu_histogram = Histogram('gpu_energy', 'GPU energy consumption', buckets=gpu_buckets or [default_gpu_buckets], registry=registry)
self.cpu_histogram = Histogram('cpu_energy', 'CPU energy consumption', buckets=cpu_buckets or [default_cpu_buckets], registry=registry)
self.dram_histogram = Histogram('dram_energy', 'DRAM energy consumption', buckets=dram_buckets or [default_dram_buckets], registry=registry) 7. Which module are these going to be in? zeus.metrics?zeus.metrics.py 8. In CounterMetric, shouldn't it be an mp.Queue?My bad, should be mp.Queue! 9. I suppose when the polling process receives a "stop", it should push the energy value one last time to Prometheus before terminating.Code Modified! 10. In push_to_gateway (I'm assuming this is also part of the Prometheus client library), what is job? Would the user want to customize this?The job parameter in the push_to_gateway() function which is part of the Prometheus Client library, serves as an tag or identifier for a specific job or process. It groups the metrics that are pushed to the Prometheus Push Gateway under a job label. # for energy monitoring metrics
push_to_gateway('localhost:9091', job='energy_monitoring', registry=registry)
# for power monitoring metrics
push_to_gateway('localhost:9091', job='power_monitoring', registry=registry) Users might want to customize the name of the job since how metrics are viewed and organized in Prometheus could differ by name. |
Sounds good, let's keep the current registry design! But if it ends up being that we never expose any Prometheus client APIs to our users (e.g., I think users never call
Do you think a more power/energy-specific naming is appropriate, especially is Zeus class names and Prometheus client class names are very similar? Like
Cool, now this can internally call
I see, yeah. Since CPU/GPU/DRAM energy are distinct, it isn't too strange for them to be separate metrics. It should be important to have some consistent naming convention so that users can understand that a set of CPU/GPU/DRAM metrics are from the same Zeus Histogram metric. I guess something like
Actually I think I might have read something strangely last time 😅 With the modified code snippet,
Under the current design, are users ever supposed to call |
I'm little curious under what circumstances monitor.end_window would be called on a window that was never started. Since our polling method operates on a single child process, which runs synchronously without interruption, and we consistently push metrics without requiring end_window, I’m not sure when this scenario would occur. |
I was eyeballing this code snippet: def _poll(self, name: str, pipe: mp.Queue, monitor: monitor, update_period: float):
metric = Counter(
'counter_metric', # Static metric name
'Measures total energy consumption',
['window_name'], # Label for the dynamic window name
registry = registry
)
while True: # Not stopped yet. (Until you get the signal "stop")
if not pipe.empty():
signal = pipe.get()
if signal == "stop":
measurement = monitor.end_window(f"__CounterMetric_{name}")
total_energy = sum(measurement.gpu_energy.values() + measurement.cpu_energy.values() + measurement.dram_energy.values())
metric.labels(window_name=f"__CounterMetric_{name}").inc(total_energy)
push_to_gateway('localhost:9091', job='energy_consumption_total', registry=registry)
break
monitor.begin_window(f"__CounterMetric_{name}")
time.sleep(update_period)
measurement = monitor.end_window(f"__CounterMetric_{name}")
total_energy = sum(measurement.gpu_energy.values() + measurement.cpu_energy.values() + measurement.dram_energy.values())
metric.labels(window_name=f"__CounterMetric_{name}").inc(total_energy)
push_to_gateway('localhost:9091', job='energy_consumption_total', registry=registry)
# Doesn't really do much, because the counter metric has been pushed to Prometheus continuously over time (by the polling process)
metric.end_window("name")
|
Got it, thanks for the explanation! I'll also update the code based on your feedback in terms of naming conventions and constructor. |
Integration of Prometheus Push Gateway and Energy Metrics Collection in Zeus
Issue #30
Motivation
The goal of this proposal is to streamline the integration of energy consumption metrics (e.g., for GPU, CPU, DRAM) into Prometheus using a push-based model with the Prometheus Push Gateway. It also proposes implementing support for Counters, Histograms to provide detailed insights into energy consumption and Gauges for power consumption trends across different windows and time periods.
Background
In Zeus, the framework already measures and records energy consumption for different components, but storing and visualizing these metrics in a time-series database has been challenging. Prometheus is a highly efficient monitoring system that provides real-time, multi-dimensional data collection, and visualization.
Prometheus typically follows a pull model where it scrapes metrics from an endpoint. However, in environments where long-running processes or jobs run on distributed systems, a push model becomes necessary. This is achieved using Prometheus Push Gateway, which allows intermediate results (such as energy metrics) from batch jobs to be pushed from applications.
Proposed Design
The integration will introduce three main metric types to capture energy data from Zeus:
Histogram: Records energy consumption over different windows, with custom bucket ranges based on hardware.
Counter: Tracks the cumulative energy consumed across multiple windows over time. This metric is especially useful for long-running processes.
Gauge: This will later be implemented to monitor power consumption at specific instances.
Each metric will be registered with a Prometheus CollectorRegistry, which organizes and tracks all metrics before they are pushed to the Push Gateway.
How the Collector Registry in Prometheus works in detail
Implementation
Usage Example:
Code Breakdown:
Collector Registry
In both classes, the
CollectorRegistry
manages the metrics to be pushed to Prometheus. While a default registry can be used, a custom registry is being employed here for better separation and control over metrics.Abstract Base Class (ABC)
The
Metric
class is an abstract base class (ABC) that defines a common interface for different types of metrics. It provides two abstract methods,begin_window
andend_window
, that must be implemented by any subclasses, such asHistogramMetric
orCounterMetric
.HistogramMetric Class
The HistogramMetric class tracks energy consumption for GPU, CPU, and DRAM across pre-defined buckets. It uses prometheus_client. Histogram to observe energy consumption at the end of each window.
Constructor:
__init__(self, monitor, bucket_ranges=None)
monitor
: This parameter is an instance ofZeusMonitor
, which handles the collection of energy data from different hardware components like GPU, CPU, and DRAM.bucket_ranges
: An optional parameter that defines the bucket ranges for the histogram. If not provided, a default set of bucket ranges will be used.Histogram Initialization (
self.energy_histogram
):total_energy
: This is the name of the histogram metricTotal energy consumed
: This is the description of the metric['component', 'window']
: This label This will ensure that the histogram tracks both the component (e.g., CPU, GPU, DRAM) and the specific window during which energy consumption occurred.buckets=bucket_ranges
: The provided bucket ranges define how the data will be aggregated.registry=registry
:This argument links the histogram to a PrometheusCollectorRegistry
, which organizes and manages the collected metrics.begin_window(self, name: str)
callsself.monitor.begin_window(f"__HistogramMetric_{name}")
, whereself.monitor
is an instance of ZeusMonitor.end_window(self, name: str)
retrieves the collected energy data, updates the histogram, triggers warnings for high energy usage, and pushes the metrics to the Prometheus Push Gateway.self.energy_histogram.labels(component="gpu").observe(total_gpu_energy)
call registers the GPU energy in the Prometheus histogram under the "gpu" label.push_to_gateway('localhost:9091', job='energy_monitoring', registry=registry)
pushes the collected energy data to the Prometheus Push Gateway. This makes the histogram data available for external systems to query and visualize.CounterMetric Class
The CounterMetric class continuously tracks cumulative energy consumption and pushes the updates at specified intervals (e.g., every 5 seconds).
__init__(self, monitor, update_period: int):
monitor
: An instance of ZeusMonitor used to track energy data.update_period
: An integer specifying the frequency (in seconds) for polling the energy data.self.queue = mp.Queue()
creates a queue to communicate with the child process. This will be used to signal the child process to stop.self.proc = mp.Process(target=self._poll, args=(name, self.queue, self.monitor, self. update_period))
creates a new process to run the _poll method. The process will be passed the name, queue, monitor, and polling period as arguments.self.proc.start()
starts the child process, which will handle the background polling of energy data while the main process continues its execution.self.queue.put("stop")
places a "stop" message in the queue to notify the child process to terminate.self.proc.join()
waits for the child process to finish execution and terminate cleanly before proceeding.Polling Method
_poll
method is executed by the child process. It polls the energy consumption data at regular intervals (as specified byupdate_period
) and increments the Prometheus counter metric with the energy data.while len(pipe) == 0
). This checks if the queue is empty, which means no stop signal has been sent yet.metric.labels(window_name=f"__CounterMetric_{name}").inc(total_energy)
increments the counter with the energy data from the measurement. It associates this data with the specific window using the window_name label.Next Steps
The text was updated successfully, but these errors were encountered: