diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..308f9f2 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [ dev ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: install references + run: sudo apt-get install libgtest-dev -y && cd /usr/src/gtest && sudo cmake CMakeLists.txt && sudo make && cd lib && sudo cp *.a /usr/lib && sudo apt install build-essential && sudo apt install git && sudo apt-get install -y cppcheck && sudo apt-get install valgrind && sudo apt-get install -y lcov && sudo apt-get install -y gcovr + + - name: build + run: mkdir build && cd build && cmake .. && make + + - name: tests_under_valgrind + run: valgrind --leak-check=full --track-origins=yes ./build/tests && valgrind --leak-check=full --track-origins=yes --child-silent-after-fork=yes ./build/tests_procs + + - name: stress_tests_avg_time + run: ./build/stress_test && ./build/stress_test_procs + + - name: code coverage + run: lcov -t "HW_2" -o coverage.txt -c -d . && cp coverage.txt ../ + + - name: upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.txt + flags: unittests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..088d412 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/cmake-build-debug +/build +/linters diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f39138f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.17) +project(HW2) +set(CMAKE_C_STANDARD 99) +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -pthread --coverage") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -pthread --coverage") + +add_compile_options("--coverage") + +add_subdirectory(lib/utils) +add_subdirectory(lib/word_search) +add_subdirectory(lib/word_search_procs) + +enable_testing() +find_package(GTest REQUIRED) +add_executable(tests tests/test.cpp) +include_directories(${GTEST_INCLUDE_DIRS}) +target_link_libraries(tests ${GTEST_LIBRARIES} utils word_search gcov) + +add_executable(tests_procs tests/test.cpp) +include_directories(${GTEST_INCLUDE_DIRS}) +target_link_libraries(tests_procs ${GTEST_LIBRARIES} utils word_search_procs gcov) + + +add_executable(stress_test tests/stress_test.cpp) +include_directories(${GTEST_INCLUDE_DIRS}) +target_link_libraries(stress_test ${GTEST_LIBRARIES} utils word_search gcov) + +add_executable(stress_test_procs tests/stress_test.cpp) +include_directories(${GTEST_INCLUDE_DIRS}) +target_link_libraries(stress_test_procs ${GTEST_LIBRARIES} utils word_search_procs gcov) diff --git a/README.md b/README.md index bcf8eb8..42012b4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ # HW2 Решение второго ИЗ из курса по углубленному программированию на языке C/C++ в технопарке вк компани! лучшего технического во вселенной + +### Вариант #17 +Сравните и выведите в консоль время работы последовательного и параллельного с использованием нескольких процессов алгоритмов, каждый из которых выделяет в динамической памяти символьный массив размером 100 Мб и выполняет поиск самого длинного слова в тексте. Словом считается последовательность, состоящая из букв и ограниченная пробелами. + +### Coverage +https://app.codecov.io/gh/erik770/HW2/commits?page=1 + +### Time +``` +Run ./build/stress_test && ./build/stress_test_procs +average time: 0.0613759 -w/o forks +average time: 0.0006113 -with forks +``` diff --git a/lib/utils/CMakeLists.txt b/lib/utils/CMakeLists.txt new file mode 100644 index 0000000..24cc298 --- /dev/null +++ b/lib/utils/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(utils STATIC src/utils.c include/utils.h) + +target_include_directories(utils PUBLIC include) diff --git a/lib/utils/include/utils.h b/lib/utils/include/utils.h new file mode 100644 index 0000000..056b149 --- /dev/null +++ b/lib/utils/include/utils.h @@ -0,0 +1,37 @@ +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "stdio.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SYMBOLS "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm " +#define NUMB_OF_SPACES 5 + +#define MIN_SIZE 131072 // 128КБайт +#define MAX_SIZE 10485760 // 100Мбайт(не 100 чтобы тесты очень долго не ждать каждый раз) + +#define BUFF_SIZE 1024 + +char *create_words(); + +size_t word_counter(const char *string_of_words); + +char **create_array_of_words(char *string_of_words); + +int word_cpy(char *dest, const char *src, size_t shift); + + +#if defined(__cplusplus) +} +#endif diff --git a/lib/utils/include/word_search_interface.h b/lib/utils/include/word_search_interface.h new file mode 100644 index 0000000..c0944a9 --- /dev/null +++ b/lib/utils/include/word_search_interface.h @@ -0,0 +1,13 @@ +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "utils.h" + +char *find_longest_word(char *string_of_words); + +#if defined(__cplusplus) +} +#endif diff --git a/lib/utils/src/utils.c b/lib/utils/src/utils.c new file mode 100644 index 0000000..76d73bd --- /dev/null +++ b/lib/utils/src/utils.c @@ -0,0 +1,99 @@ +#include "utils.h" + +char *create_words() { + srand(time(NULL)); + size_t size = rand() % (MAX_SIZE - MIN_SIZE + 1) + MIN_SIZE; + + char *words = (char *) malloc(size * sizeof(char)); + if (words == NULL) { + return NULL; + } + words[0] = SYMBOLS[rand() % (strlen(SYMBOLS) - NUMB_OF_SPACES)]; + for (size_t i = 1; i < size - 1; i++) { + words[i] = SYMBOLS[rand() % strlen(SYMBOLS)]; + if (words[i] == ' ' && words[i - 1] == ' ') { + words[i] = SYMBOLS[rand() % (strlen(SYMBOLS) - NUMB_OF_SPACES)]; + } + } + if (words[size - 2] == ' ') { + words[size - 2] = '\0'; + } else { + words[size - 1] = '\0'; + } + return words; +} + +size_t word_counter(const char *string_of_words) { + if (string_of_words == NULL) { + return 0; + } + + size_t word_counter = 0; + size_t i = 0; + while (string_of_words[i] != '\0') { + if (string_of_words[i] == ' ') { + word_counter++; + } + i++; + } + word_counter++; + + return word_counter; +} + +int word_cpy(char *dest, const char *src, size_t shift) { + if(src == NULL || dest == NULL){ + return -1; + } + + size_t j = 0; + size_t i = 0; + while (src[shift + i] != ' ' && src[shift + i] != '\0') { + dest[j] = src[shift + i]; + i++; + j++; + } + dest[j] = '\0'; + return 0; +} + + +char **create_array_of_words(char *string_of_words) { + if (string_of_words == NULL) { + return NULL; + } + + size_t words_count = word_counter(string_of_words); + if (words_count == 0) { + return NULL; + } + size_t j = 0, k = 0, h = 0; + char **words_arr = (char **) malloc(words_count * sizeof(char *)); + if (words_arr == NULL) { + return NULL; + } + + for (size_t i = 0; i < words_count; ++i) { + words_arr[i] = (char *) malloc(BUFF_SIZE * sizeof(char)); + if (words_arr[i] == NULL) { + for (size_t l = i; l > 0; l--) { + free(words_arr[l - 1]); + } + free(words_arr); + return NULL; + } + } + + while (string_of_words[j] != '\0') { + if (string_of_words[j] == ' ') { + words_arr[k][h] = '\0'; + ++j; + ++k; + h = 0; + } else { + words_arr[k][h++] = string_of_words[j++]; + } + } + words_arr[k++][h++] = '\0'; + return words_arr; +} diff --git a/lib/word_search/CMakeLists.txt b/lib/word_search/CMakeLists.txt new file mode 100644 index 0000000..47b457a --- /dev/null +++ b/lib/word_search/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(word_search STATIC src/word_search.c) + +target_include_directories(word_search PUBLIC include) +target_link_libraries(word_search PUBLIC utils) diff --git a/lib/word_search/src/word_search.c b/lib/word_search/src/word_search.c new file mode 100644 index 0000000..6aa5fa5 --- /dev/null +++ b/lib/word_search/src/word_search.c @@ -0,0 +1,39 @@ +#include "word_search_interface.h" + +char *find_longest_word(char *string_of_words) { + if (string_of_words == NULL) { + return NULL; + } + + char *longest_word = (char *) calloc(BUFF_SIZE, sizeof(char)); + if (longest_word == NULL) { + return NULL; + } + + char *current_word = (char *) malloc(BUFF_SIZE * sizeof(char)); + if (current_word == NULL) { + free(longest_word); + return NULL; + } + + size_t i = 0; + + while (true) { + if (word_cpy(current_word, string_of_words, i) != 0){ + free(longest_word); + free(current_word); + return NULL; + } + i = i + strlen(current_word) + 1; + + if (strlen(current_word) > strlen(longest_word)) { + strcpy(longest_word, current_word); + } + if (string_of_words[i - 1] == '\0') { + break; + } + } + + free(current_word); + return longest_word; +} diff --git a/lib/word_search_procs/CMakeLists.txt b/lib/word_search_procs/CMakeLists.txt new file mode 100644 index 0000000..2c6f600 --- /dev/null +++ b/lib/word_search_procs/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(word_search_procs SHARED src/word_search_procs.c) + +target_include_directories(word_search_procs PUBLIC include) +target_link_libraries(word_search_procs PUBLIC utils) diff --git a/lib/word_search_procs/src/word_search_procs.c b/lib/word_search_procs/src/word_search_procs.c new file mode 100644 index 0000000..8719805 --- /dev/null +++ b/lib/word_search_procs/src/word_search_procs.c @@ -0,0 +1,129 @@ +#include "word_search_interface.h" + + +typedef struct { + long mtype; + char mtext[BUFF_SIZE]; +} message_buff; + + +void child_procs_work(char *string_of_words, size_t current_procs_numb, size_t number_of_procs, int q_id) { + char *longest_word = (char *) calloc(BUFF_SIZE, sizeof(char)); + if (longest_word == NULL) { + exit(EXIT_FAILURE); + } + char *current_word = (char *) calloc(BUFF_SIZE, sizeof(char)); + if (current_word == NULL) { + free(longest_word); + exit(EXIT_FAILURE); + } + + size_t part_size = strlen(string_of_words) / number_of_procs; + size_t right_border = 0; + if (current_procs_numb != number_of_procs - 1) { + right_border = (current_procs_numb + 1) * part_size; + } else { + right_border = strlen(string_of_words); + } + + size_t j = current_procs_numb * part_size; + size_t skipped_letters_of_word_on_border = 0; + if (j != 0) { + while (string_of_words[j] != ' ') { + j++; + skipped_letters_of_word_on_border++; + } + j++; + skipped_letters_of_word_on_border++; + } + + if (current_procs_numb == number_of_procs - 1) { + skipped_letters_of_word_on_border = 0; + } + size_t i = 0; + while (j < right_border + skipped_letters_of_word_on_border) { + while (string_of_words[j] != ' ' && string_of_words[j] != '\0') { + current_word[i] = string_of_words[j]; + i++; + j++; + } + current_word[i] = '\0'; + i = 0; + j++; + if (strlen(current_word) > strlen(longest_word)) { + strcpy(longest_word, current_word); + } + } + + message_buff q_buff = {1, ""}; + strcpy(q_buff.mtext, longest_word); + + if (msgsnd(q_id, (struct msgbuf *) &q_buff, strlen(q_buff.mtext) + 1, 0) == -1) { + free(longest_word); + free(current_word); + free(string_of_words); + exit(EXIT_FAILURE); + } + + free(string_of_words); + free(current_word); + free(longest_word); + exit(EXIT_SUCCESS); +} + + +char *find_longest_word(char *string_of_words) { + if (string_of_words == NULL) { + return NULL; + } + + size_t number_of_procs = sysconf(_SC_NPROCESSORS_ONLN); + int status = 0; + + key_t key = IPC_PRIVATE; + int q_id = msgget(key, 0660 | IPC_CREAT); + + pid_t pids[number_of_procs]; + for (size_t k = 0; k < number_of_procs; ++k) { + pids[k] = fork(); + if (pids[k] == 0) { + child_procs_work(string_of_words, k, number_of_procs, q_id); + } + } + + for (size_t i = 0; i < number_of_procs; ++i) { + if (waitpid(pids[i], &status, 0) != pids[i]) { + return NULL; + } + } + + char *longest_word = (char *) malloc(BUFF_SIZE * sizeof(char)); + size_t max_len = 0; + + for (size_t i = 0; i < number_of_procs; ++i) { + message_buff q_buff; + + if (msgrcv(q_id, (struct msgbuf *) &q_buff, BUFF_SIZE, 1, 0) == -1) { + free(string_of_words); + return NULL; + } + + if (q_buff.mtext[0] == '\0') { + return NULL; + } + + if (max_len <= strlen(q_buff.mtext)) { + free(longest_word); + longest_word = (char *) malloc(BUFF_SIZE * sizeof(char)); + + if (longest_word == NULL) { + free(string_of_words); + return NULL; + } + + strcpy(longest_word, q_buff.mtext); + max_len = strlen(q_buff.mtext); + } + } + return longest_word; +} diff --git a/tests/stress_test.cpp b/tests/stress_test.cpp new file mode 100644 index 0000000..52e31f3 --- /dev/null +++ b/tests/stress_test.cpp @@ -0,0 +1,25 @@ +#include +#include "word_search_interface.h" +#include + +#define NUM_OF_TESTS 10 + +int main() { + double timer = 0; + + for (size_t i = 0; i < NUM_OF_TESTS; i++) { + char *string_of_words = create_words(); + + clock_t begin = clock(); + char *longest_word = find_longest_word(string_of_words); + clock_t end = clock(); + + timer += (double) (end - begin) / CLOCKS_PER_SEC; + + free(string_of_words); + free(longest_word); + } + double avg_time = timer / NUM_OF_TESTS; + std::cout << "average time: " << avg_time << std::endl; + return 0; +} diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 0000000..7251583 --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,84 @@ +#include +#include "word_search_interface.h" + +TEST(word_generation, word_generation) { + char *string_of_words = create_words(); + EXPECT_FALSE(string_of_words == NULL); + free(string_of_words); +} + +TEST(word_generation, word_generation_size) { + char *string_of_words = create_words(); + EXPECT_TRUE((strlen(string_of_words) > MIN_SIZE) && (strlen(string_of_words) < MAX_SIZE)); + free(string_of_words); +} + +TEST(word_counter, word_counter_invalid_input) { + char *pseudo_string = NULL; + size_t number_of_words = word_counter(pseudo_string); + EXPECT_TRUE(number_of_words == 0); +} + +TEST(array_creating, array_creating_invalid_input) { + char *pseudo_string = NULL; + char **arr = create_array_of_words(pseudo_string); + EXPECT_TRUE(arr == NULL); +} + +TEST(array_creating, array_creating) { + char *string_of_words = create_words(); + size_t number_of_words = word_counter(string_of_words); + char **arr = create_array_of_words(string_of_words); + for (size_t i = 0; i < number_of_words; ++i) { + EXPECT_TRUE(arr[i] != NULL); + } + EXPECT_TRUE(arr != NULL); + for (size_t i = 0; i < number_of_words; ++i) { + free(arr[i]); + } + free(string_of_words); + free(arr); +} + +TEST(array_creating, array_creating_const) { + char *string_of_words = create_words(); + size_t number_of_words = word_counter(string_of_words); + char *copy = (char *) malloc(MAX_SIZE * sizeof(char)); + copy = strcpy(copy, string_of_words); + char **arr = create_array_of_words(string_of_words); + EXPECT_TRUE(strcmp(copy, string_of_words) == 0); + for (size_t i = 0; i < number_of_words; ++i) { + free(arr[i]); + } + free(arr); + free(copy); + free(string_of_words); +} + +TEST(max_len_word, max_len_word_invalid_input) { + char *pseudo_string = NULL; + char *longest_word = find_longest_word(pseudo_string); + EXPECT_TRUE(longest_word == NULL); +} + + +TEST(max_len_word, max_len_word) { + char *string_of_words = create_words(); + size_t number_of_words = word_counter(string_of_words); + char **arr = create_array_of_words(string_of_words); + char *longest_word = find_longest_word(string_of_words); + for (size_t i = 0; i < number_of_words; ++i) { + EXPECT_TRUE(strlen(longest_word) >= strlen(arr[i])); + } + for (size_t i = 0; i < number_of_words; ++i) { + free(arr[i]); + } + free(arr); + free(longest_word); + free(string_of_words); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}