Skip to content
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

Monitor mode #630

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions api/api_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def get_phase_stats(ids):
query = """
SELECT
a.phase, a.metric, a.detail_name, a.value, a.type, a.max_value, a.min_value, a.unit,
b.uri, c.description, b.filename, b.commit_hash, b.branch
b.uri, c.description, b.filename, b.commit_hash, b.branch, b.monitor_run
FROM phase_stats as a
LEFT JOIN runs as b on b.id = a.run_id
LEFT JOIN machines as c on c.id = b.machine_id
Expand Down Expand Up @@ -452,12 +452,14 @@ def get_phase_stats_object(phase_stats, case):
for phase_stat in phase_stats:
[
phase, metric_name, detail_name, value, metric_type, max_value, min_value, unit,
repo, machine_description, filename, commit_hash, branch
repo, machine_description, filename, commit_hash, branch, monitor_run
] = phase_stat # unpack

phase = phase.split('_', maxsplit=1)[1] # remove the 001_ prepended stuff again, which is only for ordering

if case == 'Repository':
if monitor_run:
key = 'monitor'
elif case == 'Repository':
key = repo # Case D : RequirementsEngineering Case
elif case == 'Branch':
key = branch # Case C_3 : SoftwareDeveloper Case
Expand Down
1 change: 1 addition & 0 deletions docker/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ CREATE TABLE runs (
logs text,
invalid_run text,
failed boolean DEFAULT false,
monitor_run boolean DEFAULT false,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone
);
Expand Down
11 changes: 9 additions & 2 deletions metric_providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def __init__(
sudo=False,
disable_buffer=True,
skip_check=False,
rootless=False,
monitor=False,
):
self._metric_name = metric_name
self._metrics = metrics
Expand All @@ -34,7 +36,8 @@ def __init__(
self._sudo = sudo
self._has_started = False
self._disable_buffer = disable_buffer
self._rootless = None
self._monitor = monitor
self._rootless = rootless
self._skip_check = skip_check

self._tmp_folder = '/tmp/green-metrics-tool'
Expand Down Expand Up @@ -159,14 +162,18 @@ def start_profiling(self, containers=None):
call_string += ' ' # space at start
call_string += ' '.join(self._extra_switches)

if self._monitor is True:
call_string += ' --monitor '
# This needs refactoring see https://github.com/green-coding-berlin/green-metrics-tool/issues/45
if (self._metrics.get('container_id') is not None) and (containers is not None):
elif (self._metrics.get('container_id') is not None) and (containers is not None):
call_string += ' -s '
call_string += ','.join(containers.keys())

if self._rootless is True:
call_string += ' --rootless '



call_string += f" > {self._filename}"

if platform.system() == "Linux":
Expand Down
5 changes: 3 additions & 2 deletions metric_providers/cpu/time/cgroup/container/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from metric_providers.base import BaseMetricProvider

class CpuTimeCgroupContainerProvider(BaseMetricProvider):
def __init__(self, resolution, rootless=False, skip_check=False):
def __init__(self, resolution, rootless=False, skip_check=False, monitor=False):
super().__init__(
metric_name='cpu_time_cgroup_container',
metrics={'time': int, 'value': int, 'container_id': str},
resolution=resolution,
unit='us',
current_dir=os.path.dirname(os.path.abspath(__file__)),
skip_check=skip_check,
rootless=rootless,
monitor=monitor,
)
self._rootless = rootless
7 changes: 4 additions & 3 deletions metric_providers/cpu/utilization/cgroup/container/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from metric_providers.base import BaseMetricProvider

class CpuUtilizationCgroupContainerProvider(BaseMetricProvider):
def __init__(self, resolution, rootless=False, skip_check=False):
def __init__(self, resolution, rootless=False, skip_check=False, monitor=False):
super().__init__(
metric_name='cpu_utilization_cgroup_container',
metrics={'time': int, 'value': int, 'container_id': str},
resolution=resolution,
unit='Ratio',
current_dir=os.path.dirname(os.path.abspath(__file__)),
skip_check = skip_check,
skip_check=skip_check,
rootless=rootless,
monitor=monitor,
)
self._rootless = rootless
136 changes: 107 additions & 29 deletions metric_providers/cpu/utilization/cgroup/container/source.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
#include <time.h>
#include <string.h> // for strtok
#include <getopt.h>
#include <dirent.h>

typedef struct container_t { // struct is a specification and this static makes no sense here
char path[BUFSIZ];
char *id;
int active;
} container_t;

// All variables are made static, because we believe that this will
Expand Down Expand Up @@ -40,27 +42,57 @@ static long int read_cpu_cgroup(FILE *fd) {
return cpu_usage;
}

static long int get_cpu_stat(char* filename, int mode) {
FILE* fd = NULL;
long int result=-1;
static int scan_directory(container_t** containers, int rootless_mode) {
struct dirent* entry;
size_t docker_prefix_len = strlen("docker-");
size_t scope_suffix_len = strlen(".scope");
int length = 0;
DIR* dir = NULL;
char my_path[BUFSIZ] = "";

fd = fopen(filename, "r");

if ( fd == NULL) {
fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Are you using --rootless mode? Errno: %d\n", filename, errno);
exit(1);
}
if(mode == 1) {
result = read_cpu_cgroup(fd);
// printf("Got cgroup: %ld", result);
if(rootless_mode) {
sprintf(my_path, "/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/", user_id, user_id);
} else {
result = read_cpu_proc(fd);
// printf("Got /proc/stat: %ld", result);
sprintf(my_path, "/sys/fs/cgroup/system.slice/");
}
fclose(fd);
return result;
}

dir = opendir(my_path);
if (!dir) {
fprintf(stderr,"Unable to scan directory for containers. Could not find folder: %s\n", my_path);
exit(-1);
}

*containers = malloc(sizeof(container_t));
//printf("old length: %d\n", length);

while ((entry = readdir(dir)) != NULL) {
// Check if the entry is a directory and matches the format
if (entry->d_type == DT_DIR &&
strstr(entry->d_name, "docker-") == entry->d_name &&
strstr(entry->d_name + docker_prefix_len, ".scope") != NULL &&
strcmp(entry->d_name + strlen(entry->d_name) - scope_suffix_len, ".scope") == 0) {
// printf("Entry %s\n", entry->d_name);
length++;
*containers = realloc(*containers, length * sizeof(container_t));
(*containers)[length-1].id = strdup(entry->d_name);
(*containers)[length-1].active = 1;
if(rootless_mode) {
sprintf((*containers)[length-1].path,
"/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/%s/cpu.stat",
user_id, user_id, entry->d_name);
} else {
sprintf((*containers)[length-1].path,
"/sys/fs/cgroup/system.slice/%s/cpu.stat",
entry->d_name);
}
}
}
//printf("Found new length: %d\n", length);

closedir(dir);
return length;
}

static int output_stats(container_t* containers, int length) {

Expand All @@ -69,28 +101,58 @@ static int output_stats(container_t* containers, int length) {
long int cpu_readings_after[length];
long int container_reading;

FILE *fd = NULL;

struct timeval now;
int i;


// Get Energy Readings, set timestamp mark
gettimeofday(&now, NULL);

for(i=0; i<length; i++) {
//printf("Looking at %s ", containers[i].path);
cpu_readings_before[i]=get_cpu_stat(containers[i].path, 1);
// printf("Looking at %s ", containers[i].path);
fd = fopen(containers[i].path, "r");
if (fd == NULL) {
// printf("Warning, container has disappeared in 'before': %s\n", containers[i].path);
containers[i].active = 0;
continue;
}
containers[i].active = 1; // containers can come back up again and thus we set active = 1
cpu_readings_before[i]=read_cpu_cgroup(fd);
fclose(fd);
}
main_cpu_reading_before = get_cpu_stat("/proc/stat", 0);

fd = fopen("/proc/stat", "r");
main_cpu_reading_before = read_cpu_proc(fd);
fclose(fd);

usleep(msleep_time*1000);


// after the sleep the containers might have disappeared

for(i=0; i<length; i++) {
cpu_readings_after[i]=get_cpu_stat(containers[i].path, 1);
if(containers[i].active == 0) continue;

fd = fopen(containers[i].path, "r");
if (fd == NULL) {
// printf("Warning, container has disappeared in 'after': %s\n", containers[i].path);
containers[i].active = 0;
continue;
}
cpu_readings_after[i]=read_cpu_cgroup(fd);
fclose(fd);
}
main_cpu_reading_after = get_cpu_stat("/proc/stat", 0);

fd = fopen("/proc/stat", "r");
main_cpu_reading_after = read_cpu_proc(fd);
fclose(fd);

// Display Energy Readings
// This is in a seperate loop, so that all energy readings are done beforehand as close together as possible
for(i=0; i<length; i++) {
if(containers[i].active == 0) continue;

container_reading = cpu_readings_after[i] - cpu_readings_before[i];
main_cpu_reading = main_cpu_reading_after - main_cpu_reading_before;

Expand Down Expand Up @@ -130,10 +192,11 @@ static int parse_containers(container_t** containers, char* containers_string, i
int length = 0;

for (; id != NULL; id = strtok(NULL, ",")) {
//printf("Token: %s\n", id);
// printf("Token: %s\n", id);
length++;
*containers = realloc(*containers, length * sizeof(container_t));
(*containers)[length-1].id = id;
(*containers)[length-1].id = strdup(id);
(*containers)[length-1].active = 1;
if(rootless_mode) {
sprintf((*containers)[length-1].path,
"/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/docker-%s.scope/cpu.stat",
Expand Down Expand Up @@ -163,7 +226,7 @@ static int check_system(int rootless_mode) {
file_path_cpu_stat = "/sys/fs/cgroup/system.slice/cpu.stat";
}
file_path_proc_stat = "/proc/stat";

FILE* fd = NULL;

fd = fopen(file_path_cpu_stat, "r");
Expand Down Expand Up @@ -192,6 +255,9 @@ static int check_system(int rootless_mode) {
int main(int argc, char **argv) {

int c;
int length = 0;
int discover_containers = 0;
int use_containers_string = 0;
int check_system_flag = 0;
int rootless_mode = 0; // docker root is default
char *containers_string = NULL; // Dynamic buffer to store optarg
Expand All @@ -207,11 +273,12 @@ int main(int argc, char **argv) {
{"help", no_argument, NULL, 'h'},
{"interval", no_argument, NULL, 'i'},
{"containers", no_argument, NULL, 's'},
{"monitor", no_argument, NULL, 'm'},
{"check", no_argument, NULL, 'c'},
{NULL, 0, NULL, 0}
};

while ((c = getopt_long(argc, argv, "ri:s:hc", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "ri:s:hmc", long_options, NULL)) != -1) {
switch (c) {
case 'h':
printf("Usage: %s [-i msleep_time] [-h]\n\n",argv[0]);
Expand All @@ -237,9 +304,13 @@ int main(int argc, char **argv) {
rootless_mode = 1;
break;
case 's':
use_containers_string = 1;
containers_string = (char *)malloc(strlen(optarg) + 1); // Allocate memory
strncpy(containers_string, optarg, strlen(optarg));
break;
case 'm':
discover_containers = 1;
break;
case 'c':
check_system_flag = 1;
break;
Expand All @@ -249,14 +320,21 @@ int main(int argc, char **argv) {
}
}

if(check_system_flag){
exit(check_system(rootless_mode));
if (discover_containers == 1 && use_containers_string == 1) {
fprintf(stderr,"Cannot discover containers and use containers string in parallel. Please choose either or.\n");
exit(-1);
} else if (use_containers_string == 1) {
length = parse_containers(&containers, containers_string, rootless_mode);
}

int length = parse_containers(&containers, containers_string, rootless_mode);
if(check_system_flag){
exit(check_system(rootless_mode));
}

while(1) {
if(discover_containers == 1) length = scan_directory(&containers, rootless_mode);
output_stats(containers, length);
if(discover_containers == 1) free(containers);
}

free(containers); // since tools is only aborted by CTRL+C this is never called, but memory is freed on program end
Expand Down
5 changes: 3 additions & 2 deletions metric_providers/memory/total/cgroup/container/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from metric_providers.base import BaseMetricProvider

class MemoryTotalCgroupContainerProvider(BaseMetricProvider):
def __init__(self, resolution, rootless=False, skip_check=False):
def __init__(self, resolution, rootless=False, skip_check=False, monitor=False):
super().__init__(
metric_name='memory_total_cgroup_container',
metrics={'time': int, 'value': int, 'container_id': str},
resolution=resolution,
unit='Bytes',
current_dir=os.path.dirname(os.path.abspath(__file__)),
skip_check=skip_check,
rootless=rootless,
monitor=monitor,
)
self._rootless = rootless
5 changes: 3 additions & 2 deletions metric_providers/network/io/cgroup/container/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from metric_providers.base import BaseMetricProvider

class NetworkIoCgroupContainerProvider(BaseMetricProvider):
def __init__(self, resolution, rootless=False, skip_check=False):
def __init__(self, resolution, rootless=False, skip_check=False, monitor=False):
super().__init__(
metric_name='network_io_cgroup_container',
metrics={'time': int, 'value': int, 'container_id': str},
resolution=resolution,
unit='Bytes',
current_dir=os.path.dirname(os.path.abspath(__file__)),
skip_check=skip_check,
rootless=rootless,
monitor=monitor,
)
self._rootless = rootless
2 changes: 1 addition & 1 deletion metric_providers/powermetrics/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self, resolution, skip_check=False):
]


def check_system(self):
def check_system(self, check_command="default", check_error_message=None, check_parallel_provider=True):
# no call to super().check_system() as we have different logic of finding the process
if self._pm_process_count > 0:
raise MetricProviderConfigurationError('Another instance of powermetrics is already running on the system!\n'
Expand Down
1 change: 1 addition & 0 deletions migrations/2024_05_15_monitor_mode.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "runs" ADD COLUMN "monitor_run" boolean DEFAULT false;
Loading
Loading