LCOV - code coverage report
Current view: top level - samples/techempower - techempower.c (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 111 193 57.5 %
Date: 2023-04-18 16:19:03 Functions: 20 24 83.3 %

          Line data    Source code
       1             : /*
       2             :  * lwan - web server
       3             :  * Copyright (c) 2014 L. A. F. Pereira <l@tia.mat.br>
       4             :  *
       5             :  * This program is free software; you can redistribute it and/or
       6             :  * modify it under the terms of the GNU General Public License
       7             :  * as published by the Free Software Foundation; either version 2
       8             :  * of the License, or any later version.
       9             :  *
      10             :  * This program is distributed in the hope that it will be useful,
      11             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  * GNU General Public License for more details.
      14             :  *
      15             :  * You should have received a copy of the GNU General Public License
      16             :  * along with this program; if not, write to the Free Software
      17             :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
      18             :  * USA.
      19             :  */
      20             : 
      21             : #include <errno.h>
      22             : #include <stdlib.h>
      23             : #include <string.h>
      24             : 
      25             : #include "lwan-private.h"
      26             : #include "lwan-cache.h"
      27             : #include "lwan-config.h"
      28             : #include "lwan-template.h"
      29             : #include "lwan-mod-lua.h"
      30             : #include "int-to-str.h"
      31             : 
      32             : #include "database.h"
      33             : #include "json.h"
      34             : 
      35             : enum db_connect_type { DB_CONN_MYSQL, DB_CONN_SQLITE };
      36             : 
      37             : static struct db_connection_params {
      38             :     enum db_connect_type type;
      39             :     union {
      40             :         struct {
      41             :             const char *user;
      42             :             const char *password;
      43             :             const char *database;
      44             :             const char *hostname;
      45             :         } mysql;
      46             :         struct {
      47             :             const char *path;
      48             :             const char **pragmas;
      49             :         } sqlite;
      50             :     };
      51             : } db_connection_params;
      52             : 
      53             : static const char hello_world[] = "Hello, World!";
      54             : static const char random_number_query[] =
      55             :     "SELECT randomNumber, id FROM world WHERE id=?";
      56             : static const char cached_random_number_query[] =
      57             :     "SELECT randomNumber, id FROM world WHERE id=?";
      58             : 
      59             : struct Fortune {
      60             :     struct {
      61             :         coro_function_t generator;
      62             : 
      63             :         int id;
      64             :         char *message;
      65             :     } item;
      66             : };
      67             : 
      68          16 : DEFINE_ARRAY_TYPE_INLINEFIRST(fortune_array, struct Fortune)
      69             : 
      70             : static const char fortunes_template_str[] =
      71             :     "<!DOCTYPE html>"
      72             :     "<html>"
      73             :     "<head><title>Fortunes</title></head>"
      74             :     "<body>"
      75             :     "<table>"
      76             :     "<tr><th>id</th><th>message</th></tr>"
      77             :     "{{#item}}"
      78             :     "<tr><td>{{item.id}}</td><td>{{item.message}}</td></tr>"
      79             :     "{{/item}}"
      80             :     "</table>"
      81             :     "</body>"
      82             :     "</html>";
      83             : 
      84             : static int fortune_list_generator(struct coro *coro, void *data);
      85             : 
      86             : #undef TPL_STRUCT
      87             : #define TPL_STRUCT struct Fortune
      88             : static const struct lwan_var_descriptor fortune_desc[] = {
      89             :     TPL_VAR_SEQUENCE(item,
      90             :                      fortune_list_generator,
      91             :                      ((const struct lwan_var_descriptor[]){
      92             :                          TPL_VAR_INT(item.id),
      93             :                          TPL_VAR_STR_ESCAPE(item.message),
      94             :                          TPL_VAR_SENTINEL,
      95             :                      })),
      96             :     TPL_VAR_SENTINEL,
      97             : };
      98             : 
      99             : static struct lwan_tpl *fortune_tpl;
     100             : 
     101             : struct hello_world_json {
     102             :     const char *message;
     103             : };
     104             : static const struct json_obj_descr hello_world_json_desc[] = {
     105             :     JSON_OBJ_DESCR_PRIM(struct hello_world_json, message, JSON_TOK_STRING),
     106             : };
     107             : 
     108             : struct db_json {
     109             :     int id;
     110             :     int randomNumber;
     111             : };
     112             : static const struct json_obj_descr db_json_desc[] = {
     113             :     JSON_OBJ_DESCR_PRIM(struct db_json, id, JSON_TOK_NUMBER),
     114             :     JSON_OBJ_DESCR_PRIM(struct db_json, randomNumber, JSON_TOK_NUMBER),
     115             : };
     116             : 
     117             : struct queries_json {
     118             :     struct db_json queries[500];
     119             :     size_t queries_len;
     120             : };
     121             : static const struct json_obj_descr queries_array_desc =
     122             :     JSON_OBJ_DESCR_OBJ_ARRAY(struct queries_json,
     123             :                              queries,
     124             :                              500,
     125             :                              queries_len,
     126             :                              db_json_desc,
     127             :                              N_ELEMENTS(db_json_desc));
     128             : 
     129          10 : static struct db *get_db(void)
     130             : {
     131             :     static __thread struct db *database;
     132             : 
     133          10 :     if (!database) {
     134           4 :         switch (db_connection_params.type) {
     135           0 :         case DB_CONN_MYSQL:
     136           0 :             database = db_connect_mysql(db_connection_params.mysql.hostname,
     137             :                                         db_connection_params.mysql.user,
     138             :                                         db_connection_params.mysql.password,
     139             :                                         db_connection_params.mysql.database);
     140           0 :             break;
     141           4 :         case DB_CONN_SQLITE:
     142           4 :             database = db_connect_sqlite(db_connection_params.sqlite.path, true,
     143             :                                          db_connection_params.sqlite.pragmas);
     144           4 :             break;
     145             :         }
     146           4 :         if (!database)
     147           0 :             lwan_status_critical("Could not connect to the database");
     148             :     }
     149             : 
     150          10 :     return database;
     151             : }
     152             : 
     153        8933 : static int append_to_strbuf(const char *bytes, size_t len, void *data)
     154             : {
     155        8933 :     struct lwan_strbuf *strbuf = data;
     156             : 
     157        8933 :     return !lwan_strbuf_append_str(strbuf, bytes, len);
     158             : }
     159             : 
     160             : static enum lwan_http_status
     161           2 : json_response_obj(struct lwan_response *response,
     162             :                   const struct json_obj_descr *descr,
     163             :                   size_t descr_len,
     164             :                   const void *data)
     165             : {
     166           2 :     if (json_obj_encode_full(descr, descr_len, data, append_to_strbuf,
     167           2 :                              response->buffer, false) != 0)
     168           0 :         return HTTP_INTERNAL_ERROR;
     169             : 
     170           2 :     response->mime_type = "application/json";
     171           2 :     return HTTP_OK;
     172             : }
     173             : 
     174             : static enum lwan_http_status
     175           8 : json_response_arr(struct lwan_response *response,
     176             :                   const struct json_obj_descr *descr,
     177             :                   const void *data)
     178             : {
     179           8 :     if (json_arr_encode_full(descr, data, append_to_strbuf, response->buffer,
     180             :                              false) != 0)
     181           0 :         return HTTP_INTERNAL_ERROR;
     182             : 
     183           8 :     response->mime_type = "application/json";
     184           8 :     return HTTP_OK;
     185             : }
     186             : 
     187           1 : LWAN_HANDLER(json)
     188             : {
     189           1 :     struct hello_world_json j = {.message = hello_world};
     190             : 
     191           1 :     request->flags |= RESPONSE_NO_EXPIRES;
     192             : 
     193           1 :     return json_response_obj(response, hello_world_json_desc,
     194             :                              N_ELEMENTS(hello_world_json_desc), &j);
     195             : }
     196             : 
     197        1115 : static bool db_query_key(struct db_stmt *stmt, struct db_json *out, int key)
     198             : {
     199        1115 :     struct db_row row = {.kind = 'i', .u.i = key + 1};
     200        1115 :     if (UNLIKELY(!db_stmt_bind(stmt, &row, 1)))
     201           0 :         return false;
     202             : 
     203             :     long random_number;
     204             :     long id;
     205        1115 :     if (UNLIKELY(!db_stmt_step(stmt, "ii", &random_number, &id)))
     206           0 :         return false;
     207             : 
     208        1115 :     out->id = (int)id;
     209        1115 :     out->randomNumber = (int)random_number;
     210             : 
     211        1115 :     return true;
     212             : }
     213             : 
     214        1115 : static inline bool db_query(struct db_stmt *stmt, struct db_json *out)
     215             : {
     216        1115 :     uint64_t random_num = lwan_random_uint64() % 10000ull;
     217        1115 :     return db_query_key(stmt, out, (int)random_num);
     218             : }
     219             : 
     220           1 : LWAN_HANDLER(db)
     221             : {
     222           1 :     struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
     223             :                                            sizeof(random_number_query) - 1);
     224             :     struct db_json db_json;
     225             : 
     226           1 :     if (UNLIKELY(!stmt)) {
     227           0 :         lwan_status_debug("preparing stmt failed");
     228           0 :         return HTTP_INTERNAL_ERROR;
     229             :     }
     230             : 
     231           1 :     bool queried = db_query(stmt, &db_json);
     232             : 
     233           1 :     db_stmt_finalize(stmt);
     234             : 
     235           1 :     if (!queried)
     236           0 :         return HTTP_INTERNAL_ERROR;
     237             : 
     238           1 :     request->flags |= RESPONSE_NO_EXPIRES;
     239             : 
     240           1 :     return json_response_obj(response, db_json_desc, N_ELEMENTS(db_json_desc),
     241             :                          &db_json);
     242             : }
     243             : 
     244           8 : LWAN_HANDLER(queries)
     245             : {
     246           8 :     enum lwan_http_status ret = HTTP_INTERNAL_ERROR;
     247           8 :     const char *queries_str = lwan_request_get_query_param(request, "queries");
     248             :     long queries;
     249             : 
     250          16 :     queries = LIKELY(queries_str)
     251           7 :                   ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1)))
     252          15 :                   : 1;
     253             : 
     254           8 :     struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
     255             :                                            sizeof(random_number_query) - 1);
     256           8 :     if (UNLIKELY(!stmt))
     257           0 :         return HTTP_INTERNAL_ERROR;
     258             : 
     259           8 :     struct queries_json qj = {.queries_len = (size_t)queries};
     260        1122 :     for (long i = 0; i < queries; i++) {
     261        1114 :         if (!db_query(stmt, &qj.queries[i]))
     262           0 :             goto out;
     263             :     }
     264             : 
     265             :     /* Avoid reallocations/copies while building response.  Each response
     266             :      * has ~32bytes.  500 queries (max) should be less than 16384 bytes,
     267             :      * so this is a good approximation.  */
     268           8 :     lwan_strbuf_grow_to(response->buffer, (size_t)(32l * queries));
     269             : 
     270           8 :     request->flags |= RESPONSE_NO_EXPIRES;
     271             : 
     272           8 :     ret = json_response_arr(response, &queries_array_desc, &qj);
     273           8 : out:
     274           8 :     db_stmt_finalize(stmt);
     275             : 
     276           8 :     return ret;
     277             : }
     278             : 
     279             : static struct cache *cached_queries_cache;
     280             : struct db_json_cached {
     281             :     struct cache_entry base;
     282             :     struct db_json db_json;
     283             : };
     284             : 
     285           0 : static struct cache_entry *cached_queries_new(const void *keyptr, void *context)
     286             : {
     287             :     struct db_json_cached *entry;
     288             :     struct db_stmt *stmt;
     289           0 :     int key = (int)(uintptr_t)keyptr;
     290             : 
     291           0 :     entry = malloc(sizeof(*entry));
     292           0 :     if (UNLIKELY(!entry))
     293           0 :         return NULL;
     294             : 
     295           0 :     stmt = db_prepare_stmt(get_db(), cached_random_number_query,
     296             :                            sizeof(cached_random_number_query) - 1);
     297           0 :     if (UNLIKELY(!stmt)) {
     298           0 :         free(entry);
     299           0 :         return NULL;
     300             :     }
     301             : 
     302           0 :     if (!db_query_key(stmt, &entry->db_json, key)) {
     303           0 :         free(entry);
     304           0 :         entry = NULL;
     305             :     }
     306             : 
     307           0 :     db_stmt_finalize(stmt);
     308             : 
     309           0 :     return (struct cache_entry *)entry;
     310             : }
     311             : 
     312           0 : static void cached_queries_free(struct cache_entry *entry, void *context)
     313             : {
     314           0 :     free(entry);
     315           0 : }
     316             : 
     317           0 : static struct cache_entry *my_cache_coro_get_and_ref_entry(
     318             :     struct cache *cache, struct lwan_request *request, int key)
     319             : {
     320             :     /* Using this function instead of cache_coro_get_and_ref_entry() will avoid
     321             :      * calling coro_defer(), which, in cases where the number of cached queries
     322             :      * is too high, will trigger reallocations of the coro_defer array (and the
     323             :      * "demotion" from the storage inlined in the coro struct to somewhere in
     324             :      * the heap).
     325             :      *
     326             :      * For large number of cached elements, too, this will reduce the number of
     327             :      * indirect calls that are performed every time a request is serviced.
     328             :      */
     329             : 
     330           0 :     for (int tries = 64; tries; tries--) {
     331             :         int error;
     332             :         struct cache_entry *ce =
     333           0 :             cache_get_and_ref_entry(cache, (void *)(uintptr_t)key, &error);
     334             : 
     335           0 :         if (LIKELY(ce))
     336           0 :             return ce;
     337             : 
     338           0 :         if (error != EWOULDBLOCK)
     339           0 :             break;
     340             : 
     341           0 :         coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE);
     342             : 
     343           0 :         if (tries > 16)
     344           0 :             lwan_request_sleep(request, (unsigned int)(tries / 8));
     345             :     }
     346             : 
     347           0 :     return NULL;
     348             : }
     349             : 
     350           0 : LWAN_HANDLER(cached_queries)
     351             : {
     352           0 :     const char *queries_str = lwan_request_get_query_param(request, "count");
     353             :     long queries;
     354             : 
     355           0 :     queries = LIKELY(queries_str)
     356           0 :                   ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1)))
     357           0 :                   : 1;
     358             : 
     359           0 :     struct queries_json qj = {.queries_len = (size_t)queries};
     360           0 :     for (long i = 0; i < queries; i++) {
     361             :         struct db_json_cached *jc;
     362           0 :         int key = (int)lwan_random_uint64() % 10000;
     363             : 
     364           0 :         jc = (struct db_json_cached *)my_cache_coro_get_and_ref_entry(
     365             :             cached_queries_cache, request, key);
     366           0 :         if (UNLIKELY(!jc))
     367           0 :             return HTTP_INTERNAL_ERROR;
     368             : 
     369           0 :         qj.queries[i] = jc->db_json;
     370             : 
     371           0 :         cache_entry_unref(cached_queries_cache, (struct cache_entry *)jc);
     372             :     }
     373             : 
     374             :     /* Avoid reallocations/copies while building response.  Each response
     375             :      * has ~32bytes.  500 queries (max) should be less than 16384 bytes,
     376             :      * so this is a good approximation.  */
     377           0 :     lwan_strbuf_grow_to(response->buffer, (size_t)(32l * queries));
     378             : 
     379           0 :     request->flags |= RESPONSE_NO_EXPIRES;
     380           0 :     return json_response_arr(response, &queries_array_desc, &qj);
     381             : }
     382             : 
     383           6 : LWAN_HANDLER(plaintext)
     384             : {
     385           6 :     lwan_strbuf_set_static(response->buffer, hello_world,
     386             :                            sizeof(hello_world) - 1);
     387             : 
     388           6 :     request->flags |= RESPONSE_NO_EXPIRES;
     389           6 :     response->mime_type = "text/plain";
     390           6 :     return HTTP_OK;
     391             : }
     392             : 
     393          36 : static int fortune_compare(const void *a, const void *b)
     394             : {
     395          36 :     const struct Fortune *fortune_a = (const struct Fortune *)a;
     396          36 :     const struct Fortune *fortune_b = (const struct Fortune *)b;
     397             : 
     398          36 :     return strcmp(fortune_a->item.message, fortune_b->item.message);
     399             : }
     400             : 
     401          13 : static bool append_fortune(struct coro *coro,
     402             :                            struct fortune_array *fortunes,
     403             :                            int id,
     404             :                            const char *message)
     405             : {
     406             :     struct Fortune *fortune;
     407             :     char *message_copy;
     408             : 
     409          13 :     message_copy = coro_strdup(coro, message);
     410          13 :     if (UNLIKELY(!message_copy))
     411           0 :         return false;
     412             : 
     413          13 :     fortune = fortune_array_append(fortunes);
     414          13 :     if (UNLIKELY(!fortune))
     415           0 :         return false;
     416             : 
     417          13 :     fortune->item.id = id;
     418          13 :     fortune->item.message = message_copy;
     419             : 
     420          13 :     return true;
     421             : }
     422             : 
     423           1 : static int fortune_list_generator(struct coro *coro, void *data)
     424             : {
     425             :     static const char fortune_query[] = "SELECT * FROM Fortune";
     426           1 :     struct Fortune *fortune = data;
     427             :     struct fortune_array fortunes;
     428             :     struct db_stmt *stmt;
     429             : 
     430           1 :     stmt = db_prepare_stmt(get_db(), fortune_query, sizeof(fortune_query) - 1);
     431           1 :     if (UNLIKELY(!stmt))
     432           0 :         return 0;
     433             : 
     434           1 :     fortune_array_init(&fortunes);
     435             : 
     436             :     long id;
     437             :     char fortune_buffer[256];
     438          13 :     while (db_stmt_step(stmt, "is", &id, &fortune_buffer, sizeof(fortune_buffer))) {
     439          12 :         if (!append_fortune(coro, &fortunes, (int)id, fortune_buffer))
     440           0 :             goto out;
     441             :     }
     442             : 
     443           1 :     if (!append_fortune(coro, &fortunes, 0,
     444             :                         "Additional fortune added at request time."))
     445           0 :         goto out;
     446             : 
     447           1 :     fortune_array_sort(&fortunes, fortune_compare);
     448             : 
     449             :     struct Fortune *iter;
     450          14 :     LWAN_ARRAY_FOREACH (&fortunes, iter) {
     451          13 :         fortune->item.id = iter->item.id;
     452          13 :         fortune->item.message = iter->item.message;
     453          13 :         coro_yield(coro, 1);
     454             :     }
     455             : 
     456           1 : out:
     457           1 :     fortune_array_reset(&fortunes);
     458           1 :     db_stmt_finalize(stmt);
     459           1 :     return 0;
     460             : }
     461             : 
     462           1 : LWAN_HANDLER(fortunes)
     463             : {
     464             :     struct Fortune fortune;
     465             : 
     466           1 :     lwan_strbuf_grow_to(response->buffer, 1500);
     467             : 
     468           1 :     if (UNLIKELY(!lwan_tpl_apply_with_buffer(fortune_tpl, response->buffer,
     469             :                                              &fortune)))
     470           0 :         return HTTP_INTERNAL_ERROR;
     471             : 
     472           1 :     request->flags |= RESPONSE_NO_EXPIRES;
     473           1 :     response->mime_type = "text/html; charset=UTF-8";
     474           1 :     return HTTP_OK;
     475             : }
     476             : 
     477           5 : LWAN_HANDLER(quit_lwan)
     478             : {
     479           5 :     exit(0);
     480             :     return HTTP_OK;
     481             : }
     482             : 
     483           5 : int main(void)
     484             : {
     485             :     struct lwan l;
     486             : 
     487           5 :     lwan_init(&l);
     488             : 
     489           5 :     if (getenv("USE_MYSQL")) {
     490           0 :         db_connection_params = (struct db_connection_params){
     491             :             .type = DB_CONN_MYSQL,
     492           0 :             .mysql.user = getenv("MYSQL_USER"),
     493           0 :             .mysql.password = getenv("MYSQL_PASS"),
     494           0 :             .mysql.hostname = getenv("MYSQL_HOST"),
     495           0 :             .mysql.database = getenv("MYSQL_DB"),
     496             :         };
     497             : 
     498           0 :         if (!db_connection_params.mysql.user)
     499           0 :             lwan_status_critical("No MySQL user provided");
     500           0 :         if (!db_connection_params.mysql.password)
     501           0 :             lwan_status_critical("No MySQL password provided");
     502           0 :         if (!db_connection_params.mysql.hostname)
     503           0 :             lwan_status_critical("No MySQL hostname provided");
     504           0 :         if (!db_connection_params.mysql.database)
     505           0 :             lwan_status_critical("No MySQL database provided");
     506             :     } else {
     507             :         static const char *pragmas[] = {"PRAGMA mmap_size=44040192",
     508             :                                         "PRAGMA journal_mode=OFF",
     509             :                                         "PRAGMA locking_mode=EXCLUSIVE", NULL};
     510           5 :         db_connection_params = (struct db_connection_params){
     511             :             .type = DB_CONN_SQLITE,
     512             :             .sqlite.path = "techempower.db",
     513             :             .sqlite.pragmas = pragmas,
     514             :         };
     515             :     }
     516             : 
     517           5 :     fortune_tpl = lwan_tpl_compile_string_full(
     518             :         fortunes_template_str, fortune_desc, LWAN_TPL_FLAG_CONST_TEMPLATE);
     519           5 :     if (!fortune_tpl)
     520           0 :         lwan_status_critical("Could not compile fortune templates");
     521             : 
     522           5 :     cached_queries_cache = cache_create_full(cached_queries_new,
     523             :                                              cached_queries_free,
     524             :                                              hash_int_new,
     525             :                                              NULL,
     526             :                                              3600 /* 1 hour */);
     527           5 :     if (!cached_queries_cache)
     528           0 :         lwan_status_critical("Could not create cached queries cache");
     529             : 
     530           5 :     lwan_main_loop(&l);
     531             : 
     532           0 :     cache_destroy(cached_queries_cache);
     533           0 :     lwan_tpl_free(fortune_tpl);
     534           0 :     lwan_shutdown(&l);
     535             : 
     536           0 :     return 0;
     537             : }

Generated by: LCOV version 1.15-2-gb9d6727