Write a post about the graph docker memory script.
All checks were successful
build-staging Build build-staging has succeeded

This commit is contained in:
Tom Alexander 2024-10-18 18:44:28 -04:00
parent 6135001004
commit 6fb98260c0
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
3 changed files with 472 additions and 0 deletions

View File

@ -0,0 +1,33 @@
set terminal svg background '#FFFFFF'
set title 'Docker Memory Usage'
set xdata time
set timefmt '%s'
set format x '%tH:%tM:%tS'
# Please note this is in SI units (base 10), not IEC (base 2). So, for example, this would show a Gigabyte, not a Gibibyte.
set format y '%.0s%cB'
set datafile separator "|"
plot "-" using 1:2 title 'exciting\_bohr' with lines, "-" using 1:2 title 'jovial\_chandrasekhar' with lines
0|512000
4|512000
9|512000
13|4866441
18|3166699
23|3128950
27|3128950
32|3128950
35|32547799
40|4329570
e
0|528384
5|516096
9|561152
14|561152
18|561152
23|561152
28|8568963
32|8528068
37|8528068
40|8528068
45|8528068
e

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<svg
width="600" height="480"
viewBox="0 0 600 480"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>Gnuplot</title>
<desc>Produced by GNUPLOT 6.0 patchlevel 2 </desc>
<g id="gnuplot_canvas">
<rect x="0" y="0" width="600" height="480" fill="#ffffff"/>
<defs>
<circle id='gpDot' r='0.5' stroke-width='0.5' stroke='currentColor'/>
<path id='gpPt0' stroke-width='0.222' stroke='currentColor' d='M-1,0 h2 M0,-1 v2'/>
<path id='gpPt1' stroke-width='0.222' stroke='currentColor' d='M-1,-1 L1,1 M1,-1 L-1,1'/>
<path id='gpPt2' stroke-width='0.222' stroke='currentColor' d='M-1,0 L1,0 M0,-1 L0,1 M-1,-1 L1,1 M-1,1 L1,-1'/>
<rect id='gpPt3' stroke-width='0.222' stroke='currentColor' x='-1' y='-1' width='2' height='2'/>
<rect id='gpPt4' stroke-width='0.222' stroke='currentColor' fill='currentColor' x='-1' y='-1' width='2' height='2'/>
<circle id='gpPt5' stroke-width='0.222' stroke='currentColor' cx='0' cy='0' r='1'/>
<use xlink:href='#gpPt5' id='gpPt6' fill='currentColor' stroke='none'/>
<path id='gpPt7' stroke-width='0.222' stroke='currentColor' d='M0,-1.33 L-1.33,0.67 L1.33,0.67 z'/>
<use xlink:href='#gpPt7' id='gpPt8' fill='currentColor' stroke='none'/>
<use xlink:href='#gpPt7' id='gpPt9' stroke='currentColor' transform='rotate(180)'/>
<use xlink:href='#gpPt9' id='gpPt10' fill='currentColor' stroke='none'/>
<use xlink:href='#gpPt3' id='gpPt11' stroke='currentColor' transform='rotate(45)'/>
<use xlink:href='#gpPt11' id='gpPt12' fill='currentColor' stroke='none'/>
<path id='gpPt13' stroke-width='0.222' stroke='currentColor' d='M0,1.330 L1.265,0.411 L0.782,-1.067 L-0.782,-1.076 L-1.265,0.411 z'/>
<use xlink:href='#gpPt13' id='gpPt14' fill='currentColor' stroke='none'/>
<filter id='textbox' filterUnits='objectBoundingBox' x='0' y='0' height='1' width='1'>
<feFlood flood-color='#FFFFFF' flood-opacity='1' result='bgnd'/>
<feComposite in='SourceGraphic' in2='bgnd' operator='atop'/>
</filter>
<filter id='greybox' filterUnits='objectBoundingBox' x='0' y='0' height='1' width='1'>
<feFlood flood-color='lightgrey' flood-opacity='1' result='grey'/>
<feComposite in='SourceGraphic' in2='grey' operator='atop'/>
</filter>
</defs>
<g fill="none" color="#FFFFFF" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,444.00 L63.53,444.00 M574.82,444.00 L565.82,444.00 '/> <g transform="translate(46.14,447.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >0B</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,388.29 L63.53,388.29 M574.82,388.29 L565.82,388.29 '/> <g transform="translate(46.14,392.19)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >5MB</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,332.57 L63.53,332.57 M574.82,332.57 L565.82,332.57 '/> <g transform="translate(46.14,336.47)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >10MB</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,276.86 L63.53,276.86 M574.82,276.86 L565.82,276.86 '/> <g transform="translate(46.14,280.76)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >15MB</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,221.15 L63.53,221.15 M574.82,221.15 L565.82,221.15 '/> <g transform="translate(46.14,225.05)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >20MB</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,165.44 L63.53,165.44 M574.82,165.44 L565.82,165.44 '/> <g transform="translate(46.14,169.34)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >25MB</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,109.72 L63.53,109.72 M574.82,109.72 L565.82,109.72 '/> <g transform="translate(46.14,113.62)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >30MB</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,54.01 L63.53,54.01 M574.82,54.01 L565.82,54.01 '/> <g transform="translate(46.14,57.91)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >35MB</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,444.00 L54.53,435.00 M54.53,54.01 L54.53,63.01 '/> <g transform="translate(54.53,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:00</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M112.34,444.00 L112.34,435.00 M112.34,54.01 L112.34,63.01 '/> <g transform="translate(112.34,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:05</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M170.15,444.00 L170.15,435.00 M170.15,54.01 L170.15,63.01 '/> <g transform="translate(170.15,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:10</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M227.96,444.00 L227.96,435.00 M227.96,54.01 L227.96,63.01 '/> <g transform="translate(227.96,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:15</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M285.77,444.00 L285.77,435.00 M285.77,54.01 L285.77,63.01 '/> <g transform="translate(285.77,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:20</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M343.58,444.00 L343.58,435.00 M343.58,54.01 L343.58,63.01 '/> <g transform="translate(343.58,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:25</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M401.39,444.00 L401.39,435.00 M401.39,54.01 L401.39,63.01 '/> <g transform="translate(401.39,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:30</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M459.20,444.00 L459.20,435.00 M459.20,54.01 L459.20,63.01 '/> <g transform="translate(459.20,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:35</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M517.01,444.00 L517.01,435.00 M517.01,54.01 L517.01,63.01 '/> <g transform="translate(517.01,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:40</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M574.82,444.00 L574.82,435.00 M574.82,54.01 L574.82,63.01 '/> <g transform="translate(574.82,465.90)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >0:00:45</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,54.01 L54.53,444.00 L574.82,444.00 L574.82,54.01 L54.53,54.01 Z '/></g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
</g>
<g id="gnuplot_plot_1" ><title>exciting_bohr</title>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<g transform="translate(507.09,75.91)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >exciting_bohr</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='rgb(148, 0, 211)' d='M515.48,72.01 L558.04,72.01 M54.53,438.30 L100.78,438.30 L158.59,438.30 L204.84,389.78 L262.65,408.71 L320.46,409.14
L366.70,409.14 L424.51,409.14 L459.20,81.33 L517.01,395.76 '/></g>
</g>
<g id="gnuplot_plot_2" ><title>jovial_chandrasekhar</title>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<g transform="translate(507.09,93.91)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="end">
<text><tspan font-family="Arial" >jovial_chandrasekhar</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='rgb( 0, 158, 115)' d='M515.48,90.01 L558.04,90.01 M54.53,438.11 L112.34,438.25 L158.59,437.75 L216.40,437.75 L262.65,437.75 L320.46,437.75
L378.27,348.52 L424.51,348.98 L482.32,348.98 L517.01,348.98 L574.82,348.98 '/></g>
</g>
<g fill="none" color="#FFFFFF" stroke="rgb( 0, 158, 115)" stroke-width="2.00" stroke-linecap="butt" stroke-linejoin="miter">
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="2.00" stroke-linecap="butt" stroke-linejoin="miter">
</g>
<g fill="none" color="black" stroke="black" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<path stroke='black' d='M54.53,54.01 L54.53,444.00 L574.82,444.00 L574.82,54.01 L54.53,54.01 Z '/></g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
<g transform="translate(314.67,30.91)" stroke="none" fill="black" font-family="Arial" font-size="12.00" text-anchor="middle">
<text><tspan font-family="Arial" >Docker Memory Usage</tspan></text>
</g>
</g>
<g fill="none" color="black" stroke="currentColor" stroke-width="1.00" stroke-linecap="butt" stroke-linejoin="miter">
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,262 @@
#+OPTIONS: html-postamble:nil
#+title: Graph Docker Container Memory Usage with Gnuplot
#+date: <2025-02-08 Sat>
#+author: Tom Alexander
#+email:
#+language: en
#+select_tags: export
#+exclude_tags: noexport
Sometimes it can be useful to build a graph of docker memory usage over time. For example, I was recently working on reducing the maximum memory of a long-running script. There certainly are heavy and complex options out there like setting up Prometheus and [[https://docs.docker.com/engine/daemon/prometheus/][configuring docker to export metrics to it]] but I threw together a small python script, using only the python standard library, that outputs gnuplot code to render a graph.
* Usage
Invoke the python script before starting any docker containers. Then, once a docker container is started, the script will start recording memory usage. Any additional docker containers that are started while the script is running will also get recorded. When no docker containers are left, the script will export gnuplot code over stdout that can then be rendered into a graph.
Each container will get its own line on the graph. All containers will have their start time aligned with the left-hand side of the graph as if they had started at the same time (so the X-axis it the number of seconds the docker container has been running, as opposed to the wall time).
If you'd like, you can insert a horizontal line at whatever memory quantity you'd like by uncommenting the src_python[:exports code]{horizontal_lines} array below. This can be useful for showing a maximum limit like the paltry 32GiB offered by Cloud Run.
* Example Invocation
#+begin_src bash
$ ./graph_docker_memory.py | gnuplot > graph.svg
INFO:root:Waiting for a docker container to exist to start recording.
INFO:root:Recorded stat jovial_chandrasekhar: 528384 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 528384 bytes
INFO:root:Recorded stat exciting_bohr: 512000 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 516096 bytes
INFO:root:Recorded stat exciting_bohr: 512000 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 561152 bytes
INFO:root:Recorded stat exciting_bohr: 512000 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 561152 bytes
INFO:root:Recorded stat exciting_bohr: 4866441 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 561152 bytes
INFO:root:Recorded stat exciting_bohr: 3166699 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 561152 bytes
INFO:root:Recorded stat exciting_bohr: 3128950 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 8568963 bytes
INFO:root:Recorded stat exciting_bohr: 3128950 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 8528068 bytes
INFO:root:Recorded stat exciting_bohr: 3128950 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 8528068 bytes
INFO:root:Recorded stat exciting_bohr: 32547799 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 8528068 bytes
INFO:root:Recorded stat exciting_bohr: 4329570 bytes
INFO:root:Recorded stat jovial_chandrasekhar: 8528068 bytes
#+end_src
You can also throw src_bash[:exports code]{tee} in there to save the gnuplot file to make manual adjustments or to render in some other fashion:
#+begin_src bash
./graph_docker_memory.py | tee graph.gnuplot | gnuplot > graph.svg
#+end_src
* Output
The output from the above run would be:
[[./files/graph.svg]]
And the gnuplot source:
#+begin_src gnuplot
set terminal svg background '#FFFFFF'
set title 'Docker Memory Usage'
set xdata time
set timefmt '%s'
set format x '%tH:%tM:%tS'
# Please note this is in SI units (base 10), not IEC (base 2). So, for example, this would show a Gigabyte, not a Gibibyte.
set format y '%.0s%cB'
set datafile separator "|"
plot "-" using 1:2 title 'exciting\_bohr' with lines, "-" using 1:2 title 'jovial\_chandrasekhar' with lines
0|512000
4|512000
9|512000
13|4866441
18|3166699
23|3128950
27|3128950
32|3128950
35|32547799
40|4329570
e
0|528384
5|516096
9|561152
14|561152
18|561152
23|561152
28|8568963
32|8528068
37|8528068
40|8528068
45|8528068
e
#+end_src
* The script
#+begin_src python
#!/usr/bin/env python
from __future__ import annotations
import json
import logging
import re
import subprocess
from dataclasses import dataclass
from datetime import datetime
from time import sleep
from typing import Collection, Final, NewType, Tuple
ContainerId = NewType("ContainerId", str)
ContainerName = NewType("ContainerName", str)
SAMPLE_INTERVAL_SECONDS: Final[int] = 2
@dataclass
class Sample:
instant: datetime
stats: dict[ContainerId, Stats]
@dataclass
class Stats:
memory_usage_bytes: int
def main():
logging.basicConfig(level=logging.INFO)
samples: list[Sample] = []
labels: dict[ContainerId, ContainerName] = {}
first_pass = True
# First wait for any docker container to exist.
while True:
sample, labels_in_sample = take_sample()
if labels_in_sample:
break
if first_pass:
first_pass = False
logging.info("Waiting for a docker container to exist to start recording.")
sleep(1)
# And then record memory until no containers exist.
while True:
sample, labels_in_sample = take_sample()
if not labels_in_sample:
break
samples.append(sample)
labels = {**labels, **labels_in_sample}
sleep(SAMPLE_INTERVAL_SECONDS)
if labels:
# Draws a red horizontal line at 32 GiB since that is the memory limit for cloud run.
write_plot(
samples,
labels,
# horizontal_lines=[(32 * 1024**3, "red", "Cloud Run Max Memory")],
)
def write_plot(
samples: Collection[Sample],
labels: dict[ContainerId, ContainerName],
,*,
horizontal_lines: Collection[Tuple[int, str, str | None]] = [],
):
starting_time_per_container = {
container_id: min(
(sample.instant for sample in samples if container_id in sample.stats)
)
for container_id in labels.keys()
}
print(
"""set terminal svg background '#FFFFFF'
set title 'Docker Memory Usage'
set xdata time
set timefmt '%s'
set format x '%tH:%tM:%tS'
# Please note this is in SI units (base 10), not IEC (base 2). So, for example, this would show a Gigabyte, not a Gibibyte.
set format y '%.0s%cB'
set datafile separator "|"
"""
)
for y_value, color, label in horizontal_lines:
print(
f'''set arrow from graph 0, first {y_value} to graph 1, first {y_value} nohead linewidth 2 linecolor rgb "{color}"'''
)
if label is not None:
print(f"""set label "{label}" at graph 0, first {y_value} offset 1,-0.5""")
# Include the horizontal lines in the range
if len(horizontal_lines) > 0:
print(f"""set yrange [*:{max(x[0] for x in horizontal_lines)}<*]""")
line_definitions = ", ".join(
[
f""""-" using 1:2 title '{gnuplot_escape(name)}' with lines"""
for container_id, name in sorted(labels.items())
]
)
print("plot", line_definitions)
for container_id in sorted(labels.keys()):
start_time = int(starting_time_per_container[container_id].timestamp())
for sample in sorted(samples, key=lambda x: x.instant):
if container_id in sample.stats:
print(
"|".join(
[
str(int((sample.instant).timestamp()) - start_time),
str(sample.stats[container_id].memory_usage_bytes),
]
)
)
print("e")
def gnuplot_escape(inp: str) -> str:
out = ""
for c in inp:
if c == "_":
out += "\\"
out += c
return out
def take_sample() -> Tuple[Sample, dict[ContainerId, ContainerName]]:
labels: dict[ContainerId, ContainerName] = {}
stats: dict[ContainerId, Stats] = {}
docker_inspect = subprocess.run(
["docker", "stats", "--no-stream", "--no-trunc", "--format", "json"],
stdout=subprocess.PIPE,
)
for container_stat in (
json.loads(l) for l in docker_inspect.stdout.decode("utf8").splitlines()
):
if not container_stat["ID"]:
# When containers are starting up, they sometimes have no ID and "--" as the name.
continue
labels[ContainerId(container_stat["ID"])] = ContainerName(
container_stat["Name"]
)
memory_usage = parse_mem_usage(container_stat["MemUsage"])
stats[ContainerId(container_stat["ID"])] = Stats(
memory_usage_bytes=memory_usage
)
for container_id, container_stat in stats.items():
logging.info(
f"Recorded stat {labels[container_id]}: {container_stat.memory_usage_bytes} bytes"
)
return Sample(instant=datetime.now(), stats=stats), labels
def parse_mem_usage(mem_usage: str) -> int:
parsed_mem_usage = re.match(
r"(?P<number>[0-9]+\.?[0-9]*)(?P<unit>[^\s]+)", mem_usage
)
if parsed_mem_usage is None:
raise Exception(f"Invalid Mem Usage: {mem_usage}")
number = float(parsed_mem_usage.group("number"))
unit = parsed_mem_usage.group("unit")
for multiplier, identifier in enumerate(["B", "KiB", "MiB", "GiB", "TiB"]):
if unit == identifier:
return int(number * (1024**multiplier))
raise Exception(f"Unrecognized unit: {unit}")
if __name__ == "__main__":
main()
#+end_src