|
| 1 | +# this file is started by executing `generate_random_rrd_data.sh` |
| 2 | +# the docker entry-point-script will not start it directly because the |
| 3 | +# execution permissions are missing. |
| 4 | +# some paths are hard coded for usage in the checkmk docker container. |
| 5 | + |
| 6 | +""" |
| 7 | +Tool to create data in rrd files. |
| 8 | +Paths are hard coded for usage inside of cmk docker container. |
| 9 | +
|
| 10 | +* using watchdog to wait for modification event to detect recent created rrd |
| 11 | + files, but after the configuration is written to the rrd files. |
| 12 | +* as no old data can be inserted into rrd files, the rrd files are then |
| 13 | + recreated using the configuration of the original rrd files |
| 14 | +* then random data for the previous 5 hours is inserted |
| 15 | +* different random data generator (random, static, sine) are used to generate |
| 16 | + the data |
| 17 | +* an internal list of modified files makes sure files are only updated once |
| 18 | +""" |
| 19 | + |
| 20 | + |
| 21 | +import math |
| 22 | +import os |
| 23 | +import random |
| 24 | +import re |
| 25 | +import time |
| 26 | +from collections import defaultdict |
| 27 | + |
| 28 | +import rrdtool |
| 29 | +from watchdog.events import PatternMatchingEventHandler |
| 30 | +from watchdog.observers import Observer |
| 31 | + |
| 32 | + |
| 33 | +def random_random_function(): |
| 34 | + choice = random.randint(0, 2) |
| 35 | + if choice == 0: |
| 36 | + value = random.randint(0, 500) |
| 37 | + return lambda x: value |
| 38 | + if choice == 1: |
| 39 | + return lambda x: random.random() * 100 |
| 40 | + if choice == 2: |
| 41 | + offset = random.random() |
| 42 | + frequency = random.random() |
| 43 | + return lambda x: math.sin(offset + x / (1000.0 * (1 + frequency))) * 100 |
| 44 | + raise Exception() |
| 45 | + |
| 46 | + |
| 47 | +def clone(original_rrd, clone_rrd): |
| 48 | + info = rrdtool.info(original_rrd) |
| 49 | + |
| 50 | + metrics = set() |
| 51 | + for key, value in info.items(): |
| 52 | + match = re.match(r"^ds\[([0-9]+)\]\.([^.]+)$", key) |
| 53 | + if match: |
| 54 | + id, ds_key = match.groups() |
| 55 | + metrics.add(id) |
| 56 | + if ds_key == "type" and value != "GAUGE": |
| 57 | + raise NotImplementedError(value) |
| 58 | + if ds_key == "minimal_heartbeat" and value != 8460: |
| 59 | + raise NotImplementedError(value) |
| 60 | + metrics_count = len(metrics) |
| 61 | + |
| 62 | + rra_info = defaultdict(dict) |
| 63 | + for key, value in info.items(): |
| 64 | + match = re.match(r"^rra\[([0-9]+)\]\.([^.]+)$", key) |
| 65 | + if match: |
| 66 | + id, rra_key = match.groups() |
| 67 | + rra_info[id][rra_key] = value |
| 68 | + |
| 69 | + dss = [] |
| 70 | + for i in range(1, metrics_count + 1): |
| 71 | + dss.append(f"DS:{i}:GAUGE:8460:U:U") # assuming fixed value here, see above |
| 72 | + rras = [] |
| 73 | + for id, rra_info in rra_info.items(): |
| 74 | + cf = rra_info["cf"] |
| 75 | + xff = rra_info["xff"] |
| 76 | + rows = rra_info["rows"] |
| 77 | + steps = rra_info["pdp_per_row"] |
| 78 | + rras.append(f"RRA:{cf}:{xff}:{steps}:{rows}") |
| 79 | + |
| 80 | + rrdtool.create( |
| 81 | + clone_rrd, |
| 82 | + "--start", |
| 83 | + "0", |
| 84 | + "--step", |
| 85 | + "60", |
| 86 | + *dss, |
| 87 | + *rras, |
| 88 | + ) |
| 89 | + |
| 90 | + return metrics_count |
| 91 | + |
| 92 | + |
| 93 | +def modify_in_place(filename): |
| 94 | + filename_clone = "/tmp/clone.rrd" |
| 95 | + metrics_count = clone(filename, filename_clone) |
| 96 | + |
| 97 | + random.seed(filename) |
| 98 | + |
| 99 | + now = int(time.time()) |
| 100 | + randoms = [random_random_function() for _ in range(metrics_count)] |
| 101 | + for t in range(now - 5 * 60 * 60, now, 60): |
| 102 | + metrics = ":".join(str(r(t)) for r in randoms) |
| 103 | + rrdtool.update(filename_clone, f"{int(t)}:{metrics}") |
| 104 | + |
| 105 | + os.rename(filename_clone, filename) |
| 106 | + |
| 107 | + # with open("original.json", "w") as original: |
| 108 | + # json.dump(rrdtool.info("Check_MK.rrd"), original, sort_keys=True, indent=4) |
| 109 | + # with open("clone.json", "w") as clone_fo: |
| 110 | + # json.dump(rrdtool.info("clone.rrd"), clone_fo, sort_keys=True, indent=4) |
| 111 | + |
| 112 | + |
| 113 | +class Handler(PatternMatchingEventHandler): |
| 114 | + def __init__(self, *arg, **kwargs): |
| 115 | + super().__init__(*arg, **kwargs) |
| 116 | + self.seen_filenames = set() |
| 117 | + |
| 118 | + def on_modified(self, event): |
| 119 | + filename = event.src_path |
| 120 | + if filename in self.seen_filenames: |
| 121 | + return |
| 122 | + self.seen_filenames.add(filename) |
| 123 | + print(f"[GRRD] {filename}") |
| 124 | + modify_in_place(filename) |
| 125 | + |
| 126 | + |
| 127 | +def main(): |
| 128 | + # hooks are called sync, so we have to fork |
| 129 | + if os.environ.get("GRRD_FORK"): |
| 130 | + pid = os.fork() |
| 131 | + if pid != 0: |
| 132 | + # parent process |
| 133 | + return |
| 134 | + # forking done |
| 135 | + |
| 136 | + observer = Observer() |
| 137 | + observer.schedule( |
| 138 | + Handler(patterns=["*.rrd"], ignore_directories=True), |
| 139 | + "/omd/sites/cmk/var/check_mk/rrd/", |
| 140 | + recursive=True, |
| 141 | + ) |
| 142 | + observer.start() |
| 143 | + try: |
| 144 | + while True: |
| 145 | + time.sleep(10) |
| 146 | + except KeyboardInterrupt: |
| 147 | + print("graceful shutdown") |
| 148 | + observer.stop() |
| 149 | + observer.join() |
| 150 | + |
| 151 | + |
| 152 | +if __name__ == "__main__": |
| 153 | + main() |
0 commit comments