├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── benchmark ├── benchmark.py └── redis_ts.py ├── cJSON ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README ├── README.md ├── cJSON.c ├── cJSON.h ├── cJSON_Utils.c ├── cJSON_Utils.h ├── test.c ├── test_utils.c └── tests │ ├── test1 │ ├── test2 │ ├── test3 │ ├── test4 │ ├── test5 │ └── test6 ├── notes.txt ├── redismodule.h ├── rmutil ├── Makefile ├── alloc.c ├── alloc.h ├── heap.c ├── heap.h ├── logging.h ├── priority_queue.c ├── priority_queue.h ├── sds.c ├── sds.h ├── sdsalloc.h ├── strings.c ├── strings.h ├── test_heap.c ├── test_priority_queue.c ├── test_util.h ├── test_vector.c ├── util.c ├── util.h ├── vector.c └── vector.h └── timeseries ├── Makefile ├── timeseries.c ├── timeseries.h ├── timeseries_test.c ├── ts_conf.c ├── ts_entry.c ├── ts_entry.h ├── ts_utils.c └── ts_utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: gcc 3 | install: make 4 | before_script: 5 | - git clone https://github.com/antirez/redis.git 6 | - cd redis 7 | - make 8 | - cd .. 9 | - ./redis/src/redis-server --loadmodule ./timeseries/timeseries.so & 10 | script: echo "TS.TEST" | ./redis/src/redis-cli -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 saginoam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | make -C cJSON/ clean all && make -C rmutil/ clean all && make -C timeseries/ clean all 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedisModuleTimeSeries 2 | 3 | ###Time series values aggregation using redis modules api 4 | 5 | # Overview 6 | 7 | RedisModuleTimeSeries implements time series data aggregation in redis. 8 | The module provides two variants APIs: single aggregation and json document. 9 | The json document API is used in order to stream reports into redis, and let 10 | redis perform the ETL on the report, thus allowing the streaming pipeline to be 11 | agnostic to the data itself. 12 | 13 | The motivation for this project arrived from what I consider a misuse of other 14 | frameworks, mainly elasticsearch, for doing time series aggregation on structured numerical data. 15 | 16 | Elasticseach is a json based search engine with analytics APIs and many other features. 17 | I consider the ELK to be state of the art framework, Its best in its domain, which is collecting, analyzing and visualize 18 | unstructured textual data, such as application logs, email analyzing, user tweets, etc. 19 | 20 | But, for collecting and analyzing structured data, which contains mostly predefined numerical 21 | parameters it is not the preferred solution, IMO, and thats what I'm trying to prove here. 22 | 23 | For such use case I would use a software that allows me to store and analyze the data in the most efficient way, 24 | considering memory usage and speed of the insert/upsert operations. 25 | 26 | Redis is excellent for this use case. Especially with the new module API, allowing much of the work to be done in redis. 27 | 28 | Benchmark results included. 29 | 30 | # API 31 | 32 | ## TS.CREATE 33 | 34 | Creates a time series key. Must be called before the call to TS.INSERT on that key. 35 | 36 | ### Parameters 37 | 38 | * name - Name of the key 39 | * Interval - The time interval for data aggregation. Allowed values: second, minute, hour, day, month, year. 40 | For example, if the inetrval is hour, all values that are inserted between 09:00-10:00 are added to the same aggregation. 41 | * init_timestamp - (Optional) The earliest time that values can be added. Default is now. 42 | It is used for aggregating old data that was not originally streamed into redis. 43 | Currently the only supported time format is: "%Y:%m:%d %H:%M:%S". A configurable time format is in roadmap. 44 | 45 | ##TS.INSERT 46 | 47 | Insert time series data to be aggregated. 48 | 49 | ### Parameters 50 | 51 | * name - Name of the key 52 | * value - the value to add to time series aggregation. 53 | * timestamp - (Optional) The time that value was added. Default is now. 54 | It is used for aggregating old data that was not originally streamed into redis. 55 | 56 | 57 | ##TS.GET 58 | 59 | Query the aggregated key for statistics. 60 | 61 | ### Parameters 62 | 63 | * name - Name of the key 64 | * operation - The calculation to perform. Allowed values: sum, avg, count. 65 | * start_time - (Optional) The start time for the aggregation. Default is now. 66 | * end_time - (Optional) The end time for the aggregation. Default is now. 67 | 68 | ##TS.INFO 69 | 70 | Get information on a time series key. Returns init timestamp, last timestamp, length and interval. 71 | 72 | ### Parameters 73 | 74 | * name - Name of the key 75 | 76 | ##TS.CREATEDOC 77 | 78 | Create a json document configuration. This is used in order to insert a json document into redis and let redis extract 79 | the information from the document and convert it into a set of TS.INSERT commands on the keys and values extracted from 80 | the document. Must be called before the call to TS.INSERTDOC on that key. 81 | 82 | ### Parameters 83 | 84 | * name - Name of the document 85 | * json - A json containing the information required in order to extract data from documents that are inserted using the 86 | TS.INSERTDOC command. The json contains the following fields: 87 | * key_fields - list of field names that will be used to create the aggregated key. 88 | * ts_fields - list of field names to perform aggregation on. 89 | * interval - The time interval for data aggregation. Allowed values: second, minute, hour, day, month, year. 90 | * timestamp - (Optional) The earliest time that values can be added. Default is now. 91 | 92 | ##TS.INSERTDOC 93 | 94 | Insert a json document to be converted to time series data. 95 | 96 | ### Parameters 97 | 98 | * name - Name of the document 99 | * json - A json containing the data to aggregate. The json document must contain all the fields that exist in the 100 | 'key_fields' and 'ts_fields' configured in TS.CREATEDOC. 101 | 102 | ## Building and running: 103 | 104 | 105 | #### Prerequisites 106 | 107 | To run the modules, a redis server that supports modules is required. 108 | In case there is no redis server, or the redis server does not support modules, get latest redis: 109 | 110 | ```sh 111 | git clone https://github.com/antirez/redis.git 112 | cd redis 113 | make 114 | 115 | # In case compatibility is broken, use this fork (https://github.com/saginoam/redis.git) 116 | ``` 117 | 118 | #### Build 119 | 120 | 121 | ```sh 122 | git clone https://github.com/saginoam/RedisModuleTimeSeries.git 123 | cd RedisModuleTimeSeries 124 | make 125 | ``` 126 | 127 | #### Run 128 | ```sh 129 | /path/to/redis-server --loadmodule ./timeseries/timeseries.so 130 | ``` 131 | 132 | ## Examples 133 | 134 | The examples of the basic API are done using redis-cli. 135 | The examples of the json documents aggregation are done using python code. 136 | 137 | ### TS.CREATE 138 | 139 | Create time series key with hour interval 140 | 141 | ``` 142 | 127.0.0.1:6379> TS.CREATE testaggregation hour 143 | OK 144 | ``` 145 | 146 | ###TS.INSERT 147 | 148 | Insert some values to time series key 149 | 150 | ``` 151 | 127.0.0.1:6379> TS.INSERT testaggregation 10.5 152 | OK 153 | ``` 154 | 155 | ``` 156 | 127.0.0.1:6379> TS.INSERT testaggregation 11.5 157 | OK 158 | ``` 159 | 160 | ###TS.GET 161 | 162 | Get aggregation values for that key. 163 | 164 | ``` 165 | 127.0.0.1:6379> TS.GET testaggregation sum 166 | 1) "22" 167 | ``` 168 | 169 | ``` 170 | 127.0.0.1:6379> TS.GET testaggregation avg 171 | 1) "11" 172 | ``` 173 | 174 | ``` 175 | 127.0.0.1:6379> TS.GET testaggregation count 176 | 1) (integer) 2 177 | ``` 178 | 179 | ###TS.INFO 180 | 181 | Get information on that timeseries 182 | 183 | ``` 184 | 127.0.0.1:6379> TS.INFO testaggregation 185 | "Start: 2016:11:26 19:00:00 End: 2016:11:26 19:00:00 len: 1 Interval: hour" 186 | ``` 187 | 188 | ###TS.CREATEDOC 189 | 190 | Create json doc configuration, to enable ingestion of reports into redis. 191 | 192 | ``` 193 | import redis 194 | import json 195 | 196 | client = redis.Redis() 197 | 198 | client.execute_command('TS.CREATEDOC', "tsdoctest", json.dumps({ 199 | "interval": "hour", 200 | "timestamp": "2016:01:01 00:00:00", 201 | "key_fields": ["userId", "deviceId"], 202 | "ts_fields": ["pagesVisited", "storageUsed", "trafficUsed"] 203 | })) 204 | ``` 205 | 206 | ###TS.INSERTDOC 207 | 208 | Insert report to redis time series module. Based on the configuration in TS.CREATEDOC 209 | 210 | ``` 211 | import redis 212 | import json 213 | 214 | client = redis.Redis() 215 | 216 | client.execute_command('TS.INSERTDOC', "tsdoctest", json.dumps({ 217 | "userId": "uid1", 218 | "deviceId": "devid1", 219 | "pagesVisited": 1, 220 | "storageUsed": 2, 221 | "trafficUsed": 3, 222 | "timesamp": "2016:01:01 00:01:00" 223 | })) 224 | 225 | ``` 226 | 227 | ## Data Stream example 228 | 229 | The power if this module can be demonstrated by simulating a data stream. 230 | Data stream can be, for example, a report collecting system that stores incoming reports into kafka and loads them into redis using logstash. 231 | In that case the data time series aggregation can be acheived with nothing more than logstash configuration without any code. 232 | 233 | #### Create time series document 234 | Run the following python code to configure the time series aggregation 235 | ``` 236 | import redis 237 | import json 238 | 239 | client = redis.Redis() 240 | 241 | client.execute_command('TS.CREATEDOC', "tsdoctest", json.dumps({ 242 | "interval": "hour", 243 | "timestamp": "2016:01:01 00:00:00", 244 | "key_fields": ["userId", "deviceId"], 245 | "ts_fields": ["pagesVisited", "storageUsed", "trafficUsed"] 246 | })) 247 | 248 | ``` 249 | 250 | #### Stream documents into redis 251 | 252 | Run the bellow code to stream documents into redis. 253 | Streaming documents into redis can be done for example using kafka and logstash. 254 | For the simplicity of the demo I use python code 255 | 256 | ``` 257 | import random 258 | import redis 259 | import json 260 | 261 | client = redis.Redis() 262 | 263 | def doc(userId, deviceId, hour, minute): 264 | return json.dumps({ 265 | "userId": userId, 266 | "deviceId": deviceId, 267 | "pagesVisited": random.randint(1, 10), 268 | "storageUsed": random.randint(1, 10), 269 | "trafficUsed": random.randint(1, 10), 270 | "timestamp": "2016:01:01 %.2d:%.2d:00" % (hour, minute) 271 | }) 272 | 273 | # Simulate a data stream such as logstash with input kafka output redis 274 | for hour in range(0, 24): 275 | for minute in range(0, 60, 5): 276 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user1", "deviceA", hour, minute)) 277 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user1", "deviceA", hour, minute)) 278 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user1", "deviceB", hour, minute)) 279 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user2", "deviceB", hour, minute)) 280 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user2", "deviceC", hour, minute)) 281 | 282 | ``` 283 | 284 | #### Query redis for time series aggregation 285 | 286 | See what keys are created 287 | ``` 288 | 127.0.0.1:6379> keys * 289 | 1) "tsdoctest:user2:deviceC:storageUsed" 290 | 2) "tsdoctest:user1:deviceA:trafficUsed" 291 | 3) "tsdoctest:user1:deviceA:storageUsed" 292 | 4) "tsdoctest:user2:deviceB:pagesVisited" 293 | 5) "tsdoctest:user2:deviceB:storageUsed" 294 | 6) "tsdoctest:user1:deviceA:pagesVisited" 295 | 7) "tsdoctest" 296 | 8) "tsdoctest:user2:deviceC:pagesVisited" 297 | 9) "tsdoctest:user1:deviceB:pagesVisited" 298 | 10) "tsdoctest:user2:deviceC:trafficUsed" 299 | 11) "tsdoctest:user1:deviceB:storageUsed" 300 | 12) "tsdoctest:user1:deviceB:trafficUsed" 301 | 13) "tsdoctest:user2:deviceB:trafficUsed" 302 | ``` 303 | 304 | Get information on specific key 305 | ``` 306 | 127.0.0.1:6379> TS.INFO tsdoctest:user2:deviceC:trafficUsed 307 | "Start: 2016:01:01 00:00:00 End: 2016:01:01 23:00:00 len: 24 Interval: hour" 308 | ``` 309 | 310 | Query a specific key for avg in a single timestamp 311 | ``` 312 | 127.0.0.1:6379> TS.GET tsdoctest:user2:deviceC:trafficUsed avg "2016:01:01 00:00:00" 313 | 1) "6.5" 314 | ``` 315 | 316 | Query a specific key for avg in a time range 317 | ``` 318 | 127.0.0.1:6379> TS.GET tsdoctest:user2:deviceC:trafficUsed avg "2016:01:01 00:00:00" "2016:01:01 03:00:00" 319 | 1) "6.5" 320 | 2) "4.75" 321 | 3) "5.666666666666667" 322 | 4) "5.75" 323 | ``` 324 | 325 | Query a specific key for documents count in a time range 326 | ``` 327 | 127.0.0.1:6379> TS.GET tsdoctest:user2:deviceC:trafficUsed count "2016:01:01 00:00:00" "2016:01:01 03:00:00" 328 | 1) (integer) 12 329 | 2) (integer) 12 330 | 3) (integer) 12 331 | 4) (integer) 12 332 | ``` 333 | 334 | ## TODO 335 | 336 | * Expiration for aggregated data 337 | * Interval duration. i.e '10 minutes' 338 | * Additional analytics APIs 339 | * key Metadata. The meta data is inserted only in the creation of the key and 340 | used later for filtering/grouping by the analytics api. 341 | * Time series metadata. Meta data that is updated on each aggregation (for example kafka offset) 342 | and can be used by the client application to ensure each document is counted 'exactly once'. 343 | * Configurable time format. 344 | 345 | # Benchmark 346 | 347 | I performed the benchmark with many configuration. Used elastic 1.7 and 5.0, 348 | performed single api call and bulk api calls, used both groovy and painless for elasticsearch upsert. 349 | 350 | Insert performance: redis 10x times faster than elasticsearch. 351 | 352 | Upsert performance: redis 10-100x times faster than elasticsearch. 353 | 354 | Memory usage: When elasticsearch was configured with '_source: disable', the memory usage was similar. 355 | When '_source: enable', the elasticsearch memory usage increased drastically (3-10 times higher). 356 | -------------------------------------------------------------------------------- /benchmark/benchmark.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from elasticsearch import Elasticsearch 3 | import redis 4 | 5 | tskey = "pytsbench" 6 | es = Elasticsearch(["localhost:9200"]) 7 | elastic_5_0 = True 8 | client = redis.Redis() 9 | 10 | rsize = int(client.info("memory")['used_memory']) 11 | 12 | num_entries = 3 13 | 14 | def info(): 15 | ''' 16 | Data: 17 | storage_used 18 | pages_visited 19 | usage_time 20 | 21 | unique ids: 22 | user_id 23 | device_id 24 | 25 | Metadata: 26 | username 27 | email 28 | account 29 | ''' 30 | 31 | 32 | def delete_all(): 33 | try: 34 | es.indices.delete(tskey) 35 | except: 36 | pass 37 | for key in client.execute_command('KEYS', tskey + "*"): 38 | client.execute_command('DEL', key) 39 | global rsize 40 | rsize = int(client.info("memory")['used_memory']) 41 | 42 | def get_timestamp(day, hour, minute): 43 | return "2016:01:%.2d %.2d:%.2d:00" % (day, hour, minute) 44 | 45 | def add_redis_entry(i, day, hour): 46 | timestamp = get_timestamp(1, 0, 0) 47 | user_id = "user_id_%d" % (i) 48 | dev_id = "device_id_%d" % (i) 49 | key = "%s_%s_%s" % (tskey, user_id, dev_id) 50 | if day == 1 and hour == 0: 51 | client.hmset(key, { 52 | "user_id": user_id, 53 | "device_id": dev_id, 54 | "username": "username%d" % (i), 55 | "email": "username%d@timeseries.com" % (i), 56 | "account": "standard" 57 | }) 58 | client.execute_command('TS.CREATE', key + "_storage_used", "hour", timestamp) 59 | client.execute_command('TS.CREATE', key + "_pages_visited", "hour", timestamp) 60 | client.execute_command('TS.CREATE', key + "_usage_time", "hour", timestamp) 61 | 62 | for e in range(1, num_entries + 1): 63 | timestamp = get_timestamp(day, hour, e) 64 | client.execute_command('TS.INSERT', key + "_storage_used", str(i * 1.1 * e), timestamp) 65 | client.execute_command('TS.INSERT', key + "_pages_visited", str(i * e), timestamp) 66 | client.execute_command('TS.INSERT', key + "_usage_time", str(i * 0.2 * e), timestamp) 67 | 68 | 69 | def add_es_entry(i, day, hour): 70 | timestamp = get_timestamp(day, hour, 0) 71 | prefix = "params." if elastic_5_0 else "" 72 | user_id = "user_id_%d" % (i) 73 | dev_id = "device_id_%d" % (i) 74 | key = "%s_%s" % (user_id, dev_id) 75 | script = "ctx._source.count += 1; " 76 | script += "ctx._source.storage_used += %sstorage_used; " % (prefix) 77 | script += "ctx._source.pages_visited += %spages_visited; " % (prefix) 78 | script += "ctx._source.usage_time += %susage_time; " % (prefix) 79 | 80 | # Can't really perform the aggregation in elastic. its too slow. 81 | #for e in range(1, num_entries + 1): 82 | for e in range(1, 2): 83 | params = { 84 | "storage_used": i * 1.1 * e, 85 | "pages_visited": i * e, 86 | "usage_time": i * 0.2 * e 87 | } 88 | upsert = { 89 | "user_id": user_id, 90 | "device_id": dev_id, 91 | "username": "username%d" % (i), 92 | "email": "username%d@timeseries.com" % (i), 93 | "account": "standard", 94 | "count": 1 95 | } 96 | upsert.update(params) 97 | script_1_7 = { 98 | "script": script, 99 | "params": params, 100 | "upsert": upsert 101 | } 102 | script_5_0 = { 103 | "script": { 104 | "inline": script, 105 | "lang": "painless", 106 | "params": params 107 | }, 108 | "upsert": upsert 109 | } 110 | body = script_5_0 if elastic_5_0 else script_1_7 111 | es.update(index=tskey, doc_type=tskey, id=key + "_" + timestamp, body=body) 112 | 113 | def add_es_entry_5_0(i, day, hour): 114 | add_es_entry(i, day, hour, True) 115 | 116 | 117 | def sizeof_fmt(num, suffix='b'): 118 | for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: 119 | if abs(num) < 1024.0: 120 | return "%3.1f %s%s" % (num, unit, suffix) 121 | num /= 1024.0 122 | return "%.1f%s%s" % (num, 'Yi', suffix) 123 | 124 | 125 | def get_redis_memory_size(): 126 | sz = int(client.info("memory")['used_memory']) - rsize 127 | return "size: %s (%d)" % (sizeof_fmt(sz), sz) 128 | 129 | def get_redis_size(thekey = tskey): 130 | redis_size = 0 131 | for key in client.execute_command('KEYS', thekey + "*"): 132 | redis_size += client.execute_command('DEBUG OBJECT', key)['serializedlength'] 133 | return "size: %s (%d)" % (sizeof_fmt(redis_size), redis_size) 134 | 135 | def get_es_size(): 136 | ind = 0 137 | try: 138 | ret = es.indices.stats(tskey) 139 | ind = ret['_all']['total']['store']['size_in_bytes'] 140 | except: 141 | pass 142 | return "size: %s (%d)" % (sizeof_fmt(ind), ind) 143 | 144 | def run_for_all(size, cb, msg, size_cb): 145 | start = datetime.now().replace(microsecond=0) 146 | for i in range(1, size + 1): 147 | for day in range(1, 31): 148 | for hour in range(0, 24): 149 | cb(i, day, hour) 150 | end = datetime.now().replace(microsecond=0) 151 | print msg, (end - start), size_cb() 152 | 153 | 154 | def do_benchmark(size): 155 | print "delete data" 156 | delete_all() 157 | print "----------------------------------------" 158 | print "benchmark size: ", size, "number of calls: ", num_entries * size * 24 * 30 159 | run_for_all(size, add_redis_entry, "redis ", get_redis_memory_size) 160 | #run_for_all(size, add_redis_hset_entry, "hset ", get_redis_hset_size) 161 | #run_for_all(size, add_redis_list_entry, "list ", get_redis_list_size) 162 | run_for_all(size, add_es_entry, "elastic ", get_es_size) 163 | #run_for_all(size, add_es_entry_5_0, "es5_0", get_es_size_5_0) 164 | print "----------------------------------------" 165 | 166 | 167 | do_benchmark(1) 168 | do_benchmark(10) 169 | #do_benchmark(100) 170 | #do_benchmark(1000) 171 | 172 | # TODO benchmark get api 173 | # TODO Use bulk api 174 | -------------------------------------------------------------------------------- /benchmark/redis_ts.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import redis 4 | import json 5 | 6 | client = redis.Redis() 7 | 8 | client.execute_command('TS.CREATEDOC', "tsdoctest", json.dumps({ 9 | "interval": "hour", 10 | "timestamp": "2016:01:01 00:00:00", 11 | "key_fields": ["userId", "deviceId"], 12 | "ts_fields": ["pagesVisited", "storageUsed", "trafficUsed"] 13 | })) 14 | 15 | def doc(userId, deviceId, hour, minute): 16 | return json.dumps({ 17 | "userId": userId, 18 | "deviceId": deviceId, 19 | "pagesVisited": random.randint(1, 10), 20 | "storageUsed": random.randint(1, 10), 21 | "trafficUsed": random.randint(1, 10), 22 | "timestamp": "2016:01:01 %.2d:%.2d:00" % (hour, minute) 23 | }) 24 | 25 | # Simulate a data stream such as logstash with input kafka output redis 26 | for hour in range(0, 24): 27 | for minute in range(0, 60, 5): 28 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user1", "deviceA", hour, minute)) 29 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user1", "deviceA", hour, minute)) 30 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user1", "deviceB", hour, minute)) 31 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user2", "deviceB", hour, minute)) 32 | client.execute_command('TS.INSERTDOC', "tsdoctest", doc("user2", "deviceC", hour, minute)) 33 | -------------------------------------------------------------------------------- /cJSON/.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | test 3 | *.o 4 | *.a 5 | *.so 6 | *.swp 7 | *.patch 8 | tags 9 | *.dylib 10 | -------------------------------------------------------------------------------- /cJSON/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | set(PROJ_CJSON cJSON) 4 | 5 | project(${PROJ_CJSON} C) 6 | 7 | file(GLOB HEADERS cJSON.h) 8 | set(SOURCES cJSON.c) 9 | 10 | add_library(${PROJ_CJSON} STATIC ${HEADERS} ${SOURCES}) 11 | if (NOT WIN32) 12 | target_link_libraries(${PROJ_CJSON} m) 13 | endif() 14 | 15 | add_library(${PROJ_CJSON}.shared SHARED ${HEADERS} ${SOURCES}) 16 | set_target_properties(${PROJ_CJSON}.shared PROPERTIES OUTPUT_NAME cJSON) 17 | if (NOT WIN32) 18 | target_link_libraries(${PROJ_CJSON}.shared m) 19 | endif() 20 | 21 | 22 | set(PROJ_CJSON_UTILS cJSON_utils) 23 | 24 | project(${PROJ_CJSON_UTILS} C) 25 | 26 | file(GLOB HEADERS_UTILS cJSON_Utils.h) 27 | set(SOURCES_UTILS cJSON_Utils.c) 28 | 29 | add_library(${PROJ_CJSON_UTILS} STATIC ${HEADERS_UTILS} ${SOURCES_UTILS}) 30 | target_link_libraries(${PROJ_CJSON_UTILS} ${PROJ_CJSON}) 31 | 32 | add_library(${PROJ_CJSON_UTILS}.shared SHARED ${HEADERS_UTILS} ${SOURCES_UTILS}) 33 | set_target_properties(${PROJ_CJSON_UTILS}.shared PROPERTIES OUTPUT_NAME cJSON_utils) 34 | target_link_libraries(${PROJ_CJSON_UTILS}.shared ${PROJ_CJSON}.shared) 35 | 36 | install (TARGETS ${PROJ_CJSON} DESTINATION lib${LIB_SUFFIX}) 37 | install (TARGETS ${PROJ_CJSON}.shared DESTINATION lib${LIB_SUFFIX}) 38 | install (FILES cJSON.h DESTINATION include/cJSON) 39 | install (TARGETS ${PROJ_CJSON_UTILS} DESTINATION lib${LIB_SUFFIX}) 40 | install (TARGETS ${PROJ_CJSON_UTILS}.shared DESTINATION lib${LIB_SUFFIX}) 41 | install (FILES cJSON_Utils.h DESTINATION include/cJSON) 42 | 43 | option(ENABLE_CJSON_TEST "Enable building cJSON test" OFF) 44 | if(ENABLE_CJSON_TEST) 45 | set(TEST_CJSON cJSON_test) 46 | add_executable(${TEST_CJSON} test.c) 47 | target_link_libraries(${TEST_CJSON} ${PROJ_CJSON}.shared) 48 | 49 | set(TEST_CJSON_UTILS cJSON_test_utils) 50 | add_executable(${TEST_CJSON_UTILS} test_utils.c) 51 | target_link_libraries(${TEST_CJSON_UTILS} ${PROJ_CJSON_UTILS}.shared) 52 | endif() 53 | -------------------------------------------------------------------------------- /cJSON/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Dave Gamble 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /cJSON/Makefile: -------------------------------------------------------------------------------- 1 | OBJ = cJSON.o 2 | LIBNAME = libcjson 3 | TESTS = test 4 | 5 | PREFIX ?= /usr/local 6 | INCLUDE_PATH ?= include/cjson 7 | LIBRARY_PATH ?= lib 8 | 9 | INSTALL_INCLUDE_PATH = $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) 10 | INSTALL_LIBRARY_PATH = $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) 11 | 12 | INSTALL ?= cp -a 13 | 14 | R_CFLAGS = -fpic $(CFLAGS) -Wall -Werror -Wstrict-prototypes -Wwrite-strings -D_POSIX_C_SOURCE=200112L 15 | 16 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo false') 17 | 18 | ## shared lib 19 | DYLIBNAME = $(LIBNAME).so 20 | DYLIBCMD = $(CC) -shared -o $(DYLIBNAME) 21 | 22 | ## create dynamic (shared) library on Darwin (base OS for MacOSX and IOS) 23 | ifeq (Darwin, $(uname_S)) 24 | DYLIBNAME = $(LIBNAME).dylib 25 | ## create dyanmic (shared) library on SunOS 26 | else ifeq (SunOS, $(uname_S)) 27 | DYLIBCMD = $(CC) -G -o $(DYLIBNAME) 28 | INSTALL = cp -r 29 | endif 30 | 31 | ## static lib 32 | STLIBNAME = $(LIBNAME).a 33 | 34 | .PHONY: all clean install 35 | 36 | all: $(STLIBNAME) $(TESTS) 37 | 38 | $(DYLIBNAME): $(OBJ) 39 | $(DYLIBCMD) $< $(LDFLAGS) 40 | 41 | $(STLIBNAME): $(OBJ) 42 | ar rcs $@ $< 43 | 44 | $(OBJ): cJSON.c cJSON.h 45 | 46 | .c.o: 47 | $(CC) -ansi -pedantic -c $(R_CFLAGS) $< 48 | 49 | $(TESTS): cJSON.c cJSON.h test.c 50 | $(CC) cJSON.c test.c -o test -lm -I. 51 | 52 | install: $(DYLIBNAME) $(STLIBNAME) 53 | mkdir -p $(INSTALL_LIBRARY_PATH) $(INSTALL_INCLUDE_PATH) 54 | $(INSTALL) cJSON.h $(INSTALL_INCLUDE_PATH) 55 | $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH) 56 | $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) 57 | 58 | uninstall: 59 | rm -rf $(INSTALL_LIBRARY_PATH)/$(DYLIBNAME) 60 | rm -rf $(INSTALL_LIBRARY_PATH)/$(STLIBNAME) 61 | rm -rf $(INSTALL_INCLUDE_PATH)/cJSON.h 62 | 63 | clean: 64 | rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) *.o 65 | -------------------------------------------------------------------------------- /cJSON/README: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | Welcome to cJSON. 24 | 25 | cJSON aims to be the dumbest possible parser that you can get your job done with. 26 | It's a single file of C, and a single header file. 27 | 28 | JSON is described best here: http://www.json.org/ 29 | It's like XML, but fat-free. You use it to move data around, store things, or just 30 | generally represent your program's state. 31 | 32 | 33 | First up, how do I build? 34 | Add cJSON.c to your project, and put cJSON.h somewhere in the header search path. 35 | For example, to build the test app: 36 | 37 | gcc cJSON.c test.c -o test -lm 38 | ./test 39 | 40 | 41 | As a library, cJSON exists to take away as much legwork as it can, but not get in your way. 42 | As a point of pragmatism (i.e. ignoring the truth), I'm going to say that you can use it 43 | in one of two modes: Auto and Manual. Let's have a quick run-through. 44 | 45 | 46 | I lifted some JSON from this page: http://www.json.org/fatfree.html 47 | That page inspired me to write cJSON, which is a parser that tries to share the same 48 | philosophy as JSON itself. Simple, dumb, out of the way. 49 | 50 | Some JSON: 51 | { 52 | "name": "Jack (\"Bee\") Nimble", 53 | "format": { 54 | "type": "rect", 55 | "width": 1920, 56 | "height": 1080, 57 | "interlace": false, 58 | "frame rate": 24 59 | } 60 | } 61 | 62 | Assume that you got this from a file, a webserver, or magic JSON elves, whatever, 63 | you have a char * to it. Everything is a cJSON struct. 64 | Get it parsed: 65 | cJSON *root = cJSON_Parse(my_json_string); 66 | 67 | This is an object. We're in C. We don't have objects. But we do have structs. 68 | What's the framerate? 69 | 70 | cJSON *format = cJSON_GetObjectItem(root,"format"); 71 | int framerate = cJSON_GetObjectItem(format,"frame rate")->valueint; 72 | 73 | 74 | Want to change the framerate? 75 | cJSON_GetObjectItem(format,"frame rate")->valueint=25; 76 | 77 | Back to disk? 78 | char *rendered=cJSON_Print(root); 79 | 80 | Finished? Delete the root (this takes care of everything else). 81 | cJSON_Delete(root); 82 | 83 | That's AUTO mode. If you're going to use Auto mode, you really ought to check pointers 84 | before you dereference them. If you want to see how you'd build this struct in code? 85 | cJSON *root,*fmt; 86 | root=cJSON_CreateObject(); 87 | cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble")); 88 | cJSON_AddItemToObject(root, "format", fmt=cJSON_CreateObject()); 89 | cJSON_AddStringToObject(fmt,"type", "rect"); 90 | cJSON_AddNumberToObject(fmt,"width", 1920); 91 | cJSON_AddNumberToObject(fmt,"height", 1080); 92 | cJSON_AddFalseToObject (fmt,"interlace"); 93 | cJSON_AddNumberToObject(fmt,"frame rate", 24); 94 | 95 | Hopefully we can agree that's not a lot of code? There's no overhead, no unnecessary setup. 96 | Look at test.c for a bunch of nice examples, mostly all ripped off the json.org site, and 97 | a few from elsewhere. 98 | 99 | What about manual mode? First up you need some detail. 100 | Let's cover how the cJSON objects represent the JSON data. 101 | cJSON doesn't distinguish arrays from objects in handling; just type. 102 | Each cJSON has, potentially, a child, siblings, value, a name. 103 | 104 | The root object has: Object Type and a Child 105 | The Child has name "name", with value "Jack ("Bee") Nimble", and a sibling: 106 | Sibling has type Object, name "format", and a child. 107 | That child has type String, name "type", value "rect", and a sibling: 108 | Sibling has type Number, name "width", value 1920, and a sibling: 109 | Sibling has type Number, name "height", value 1080, and a sibling: 110 | Sibling has type False, name "interlace", and a sibling: 111 | Sibling has type Number, name "frame rate", value 24 112 | 113 | Here's the structure: 114 | typedef struct cJSON { 115 | struct cJSON *next,*prev; 116 | struct cJSON *child; 117 | 118 | int type; 119 | 120 | char *valuestring; 121 | int valueint; 122 | double valuedouble; 123 | 124 | char *string; 125 | } cJSON; 126 | 127 | By default all values are 0 unless set by virtue of being meaningful. 128 | 129 | next/prev is a doubly linked list of siblings. next takes you to your sibling, 130 | prev takes you back from your sibling to you. 131 | Only objects and arrays have a "child", and it's the head of the doubly linked list. 132 | A "child" entry will have prev==0, but next potentially points on. The last sibling has next=0. 133 | The type expresses Null/True/False/Number/String/Array/Object, all of which are #defined in 134 | cJSON.h 135 | 136 | A Number has valueint and valuedouble. If you're expecting an int, read valueint, if not read 137 | valuedouble. 138 | 139 | Any entry which is in the linked list which is the child of an object will have a "string" 140 | which is the "name" of the entry. When I said "name" in the above example, that's "string". 141 | "string" is the JSON name for the 'variable name' if you will. 142 | 143 | Now you can trivially walk the lists, recursively, and parse as you please. 144 | You can invoke cJSON_Parse to get cJSON to parse for you, and then you can take 145 | the root object, and traverse the structure (which is, formally, an N-tree), 146 | and tokenise as you please. If you wanted to build a callback style parser, this is how 147 | you'd do it (just an example, since these things are very specific): 148 | 149 | void parse_and_callback(cJSON *item,const char *prefix) 150 | { 151 | while (item) 152 | { 153 | char *newprefix=malloc(strlen(prefix)+strlen(item->name)+2); 154 | sprintf(newprefix,"%s/%s",prefix,item->name); 155 | int dorecurse=callback(newprefix, item->type, item); 156 | if (item->child && dorecurse) parse_and_callback(item->child,newprefix); 157 | item=item->next; 158 | free(newprefix); 159 | } 160 | } 161 | 162 | The prefix process will build you a separated list, to simplify your callback handling. 163 | The 'dorecurse' flag would let the callback decide to handle sub-arrays on it's own, or 164 | let you invoke it per-item. For the item above, your callback might look like this: 165 | 166 | int callback(const char *name,int type,cJSON *item) 167 | { 168 | if (!strcmp(name,"name")) { /* populate name */ } 169 | else if (!strcmp(name,"format/type") { /* handle "rect" */ } 170 | else if (!strcmp(name,"format/width") { /* 800 */ } 171 | else if (!strcmp(name,"format/height") { /* 600 */ } 172 | else if (!strcmp(name,"format/interlace") { /* false */ } 173 | else if (!strcmp(name,"format/frame rate") { /* 24 */ } 174 | return 1; 175 | } 176 | 177 | Alternatively, you might like to parse iteratively. 178 | You'd use: 179 | 180 | void parse_object(cJSON *item) 181 | { 182 | int i; for (i=0;ichild; 194 | while (subitem) 195 | { 196 | // handle subitem 197 | if (subitem->child) parse_object(subitem->child); 198 | 199 | subitem=subitem->next; 200 | } 201 | } 202 | 203 | Of course, this should look familiar, since this is just a stripped-down version 204 | of the callback-parser. 205 | 206 | This should cover most uses you'll find for parsing. The rest should be possible 207 | to infer.. and if in doubt, read the source! There's not a lot of it! ;) 208 | 209 | 210 | In terms of constructing JSON data, the example code above is the right way to do it. 211 | You can, of course, hand your sub-objects to other functions to populate. 212 | Also, if you find a use for it, you can manually build the objects. 213 | For instance, suppose you wanted to build an array of objects? 214 | 215 | cJSON *objects[24]; 216 | 217 | cJSON *Create_array_of_anything(cJSON **items,int num) 218 | { 219 | int i;cJSON *prev, *root=cJSON_CreateArray(); 220 | for (i=0;i<24;i++) 221 | { 222 | if (!i) root->child=objects[i]; 223 | else prev->next=objects[i], objects[i]->prev=prev; 224 | prev=objects[i]; 225 | } 226 | return root; 227 | } 228 | 229 | and simply: Create_array_of_anything(objects,24); 230 | 231 | cJSON doesn't make any assumptions about what order you create things in. 232 | You can attach the objects, as above, and later add children to each 233 | of those objects. 234 | 235 | As soon as you call cJSON_Print, it renders the structure to text. 236 | 237 | 238 | 239 | The test.c code shows how to handle a bunch of typical cases. If you uncomment 240 | the code, it'll load, parse and print a bunch of test files, also from json.org, 241 | which are more complex than I'd care to try and stash into a const char array[]. 242 | 243 | 244 | Enjoy cJSON! 245 | 246 | 247 | - Dave Gamble, Aug 2009 248 | -------------------------------------------------------------------------------- /cJSON/README.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | 23 | Welcome to cJSON. 24 | ----------------- 25 | 26 | cJSON aims to be the dumbest possible parser that you can get your job done with. 27 | It's a single file of C, and a single header file. 28 | 29 | JSON is described best here: http://www.json.org/ 30 | It's like XML, but fat-free. You use it to move data around, store things, or just 31 | generally represent your program's state. 32 | 33 | First up, how do I build? 34 | Add cJSON.c to your project, and put cJSON.h somewhere in the header search path. 35 | For example, to build the test app: 36 | 37 | gcc cJSON.c test.c -o test -lm 38 | ./test 39 | 40 | As a library, cJSON exists to take away as much legwork as it can, but not get in your way. 41 | As a point of pragmatism (i.e. ignoring the truth), I'm going to say that you can use it 42 | in one of two modes: Auto and Manual. Let's have a quick run-through. 43 | 44 | I lifted some JSON from this page: http://www.json.org/fatfree.html 45 | That page inspired me to write cJSON, which is a parser that tries to share the same 46 | philosophy as JSON itself. Simple, dumb, out of the way. 47 | 48 | Some JSON: 49 | ---------- 50 | 51 | { 52 | "name": "Jack (\"Bee\") Nimble", 53 | "format": { 54 | "type": "rect", 55 | "width": 1920, 56 | "height": 1080, 57 | "interlace": false, 58 | "frame rate": 24 59 | } 60 | } 61 | 62 | Assume that you got this from a file, a webserver, or magic JSON elves, whatever, 63 | you have a char * to it. Everything is a cJSON struct. 64 | Get it parsed: 65 | 66 | cJSON * root = cJSON_Parse(my_json_string); 67 | 68 | This is an object. We're in C. We don't have objects. But we do have structs. 69 | What's the framerate? 70 | 71 | cJSON * format = cJSON_GetObjectItem(root,"format"); 72 | int framerate = cJSON_GetObjectItem(format,"frame rate")->valueint; 73 | 74 | Want to change the framerate? 75 | 76 | cJSON_GetObjectItem(format,"frame rate")->valueint = 25; 77 | 78 | Back to disk? 79 | 80 | char * rendered = cJSON_Print(root); 81 | 82 | Finished? Delete the root (this takes care of everything else). 83 | 84 | cJSON_Delete(root); 85 | 86 | That's AUTO mode. If you're going to use Auto mode, you really ought to check pointers 87 | before you dereference them. If you want to see how you'd build this struct in code? 88 | 89 | cJSON *root,*fmt; 90 | root = cJSON_CreateObject(); 91 | cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble")); 92 | cJSON_AddItemToObject(root, "format", fmt = cJSON_CreateObject()); 93 | cJSON_AddStringToObject(fmt, "type", "rect"); 94 | cJSON_AddNumberToObject(fmt, "width", 1920); 95 | cJSON_AddNumberToObject(fmt, "height", 1080); 96 | cJSON_AddFalseToObject (fmt, "interlace"); 97 | cJSON_AddNumberToObject(fmt, "frame rate", 24); 98 | 99 | Hopefully we can agree that's not a lot of code? There's no overhead, no unnecessary setup. 100 | Look at test.c for a bunch of nice examples, mostly all ripped off the json.org site, and 101 | a few from elsewhere. 102 | 103 | What about manual mode? First up you need some detail. 104 | Let's cover how the cJSON objects represent the JSON data. 105 | cJSON doesn't distinguish arrays from objects in handling; just type. 106 | Each cJSON has, potentially, a child, siblings, value, a name. 107 | 108 | The root object has: Object Type and a Child 109 | The Child has name "name", with value "Jack ("Bee") Nimble", and a sibling: 110 | Sibling has type Object, name "format", and a child. 111 | That child has type String, name "type", value "rect", and a sibling: 112 | Sibling has type Number, name "width", value 1920, and a sibling: 113 | Sibling has type Number, name "height", value 1080, and a sibling: 114 | Sibling has type False, name "interlace", and a sibling: 115 | Sibling has type Number, name "frame rate", value 24 116 | 117 | Here's the structure: 118 | --------------------- 119 | 120 | typedef struct cJSON { 121 | struct cJSON *next,*prev; 122 | struct cJSON *child; 123 | 124 | int type; 125 | 126 | char *valuestring; 127 | int valueint; 128 | double valuedouble; 129 | 130 | char *string; 131 | } cJSON; 132 | 133 | By default all values are 0 unless set by virtue of being meaningful. 134 | 135 | next/prev is a doubly linked list of siblings. next takes you to your sibling, 136 | prev takes you back from your sibling to you. 137 | Only objects and arrays have a "child", and it's the head of the doubly linked list. 138 | A "child" entry will have prev==0, but next potentially points on. The last sibling has next=0. 139 | The type expresses Null/True/False/Number/String/Array/Object, all of which are #defined in 140 | cJSON.h 141 | 142 | A Number has valueint and valuedouble. If you're expecting an int, read valueint, if not read 143 | valuedouble. 144 | 145 | Any entry which is in the linked list which is the child of an object will have a "string" 146 | which is the "name" of the entry. When I said "name" in the above example, that's "string". 147 | "string" is the JSON name for the 'variable name' if you will. 148 | 149 | Now you can trivially walk the lists, recursively, and parse as you please. 150 | You can invoke cJSON_Parse to get cJSON to parse for you, and then you can take 151 | the root object, and traverse the structure (which is, formally, an N-tree), 152 | and tokenise as you please. If you wanted to build a callback style parser, this is how 153 | you'd do it (just an example, since these things are very specific): 154 | 155 | void parse_and_callback(cJSON *item,const char *prefix) 156 | { 157 | while (item) 158 | { 159 | char *newprefix = malloc(strlen(prefix) + strlen(item->name) + 2); 160 | sprintf(newprefix,"%s/%s",prefix,item->name); 161 | int dorecurse = callback(newprefix, item->type, item); 162 | if (item->child && dorecurse) parse_and_callback(item->child, newprefix); 163 | item = item->next; 164 | free(newprefix); 165 | } 166 | } 167 | 168 | The prefix process will build you a separated list, to simplify your callback handling. 169 | The 'dorecurse' flag would let the callback decide to handle sub-arrays on it's own, or 170 | let you invoke it per-item. For the item above, your callback might look like this: 171 | 172 | int callback(const char *name,int type,cJSON *item) 173 | { 174 | if (!strcmp(name,"name")) { /* populate name */ } 175 | else if (!strcmp(name,"format/type") { /* handle "rect" */ } 176 | else if (!strcmp(name,"format/width") { /* 800 */ } 177 | else if (!strcmp(name,"format/height") { /* 600 */ } 178 | else if (!strcmp(name,"format/interlace") { /* false */ } 179 | else if (!strcmp(name,"format/frame rate") { /* 24 */ } 180 | return 1; 181 | } 182 | 183 | Alternatively, you might like to parse iteratively. 184 | You'd use: 185 | 186 | void parse_object(cJSON *item) 187 | { 188 | int i; 189 | for (i = 0 ; i < cJSON_GetArraySize(item) ; i++) 190 | { 191 | cJSON * subitem = cJSON_GetArrayItem(item, i); 192 | // handle subitem. 193 | } 194 | } 195 | 196 | Or, for PROPER manual mode: 197 | 198 | void parse_object(cJSON * item) 199 | { 200 | cJSON *subitem = item->child; 201 | while (subitem) 202 | { 203 | // handle subitem 204 | if (subitem->child) parse_object(subitem->child); 205 | 206 | subitem = subitem->next; 207 | } 208 | } 209 | 210 | Of course, this should look familiar, since this is just a stripped-down version 211 | of the callback-parser. 212 | 213 | This should cover most uses you'll find for parsing. The rest should be possible 214 | to infer.. and if in doubt, read the source! There's not a lot of it! ;) 215 | 216 | In terms of constructing JSON data, the example code above is the right way to do it. 217 | You can, of course, hand your sub-objects to other functions to populate. 218 | Also, if you find a use for it, you can manually build the objects. 219 | For instance, suppose you wanted to build an array of objects? 220 | 221 | cJSON * objects[24]; 222 | 223 | cJSON * Create_array_of_anything(cJSON ** items, int num) 224 | { 225 | int i; 226 | cJSON * prev, * root = cJSON_CreateArray(); 227 | for (i = 0 ; i < 24 ; i++) 228 | { 229 | if (!i) root->child = objects[i]; 230 | else prev->next = objects[i], objects[i]->prev = prev; 231 | prev = objects[i]; 232 | } 233 | return root; 234 | } 235 | 236 | and simply: Create_array_of_anything(objects, 24); 237 | 238 | cJSON doesn't make any assumptions about what order you create things in. 239 | You can attach the objects, as above, and later add children to each 240 | of those objects. 241 | 242 | As soon as you call cJSON_Print, it renders the structure to text. 243 | 244 | The test.c code shows how to handle a bunch of typical cases. If you uncomment 245 | the code, it'll load, parse and print a bunch of test files, also from json.org, 246 | which are more complex than I'd care to try and stash into a const char array[]. 247 | 248 | Enjoy cJSON! 249 | ----------------------- 250 | 251 | - Dave Gamble, Aug 2009 252 | -------------------------------------------------------------------------------- /cJSON/cJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | /* cJSON Types: */ 32 | #define cJSON_False (1 << 0) 33 | #define cJSON_True (1 << 1) 34 | #define cJSON_NULL (1 << 2) 35 | #define cJSON_Number (1 << 3) 36 | #define cJSON_String (1 << 4) 37 | #define cJSON_Array (1 << 5) 38 | #define cJSON_Object (1 << 6) 39 | 40 | #define cJSON_IsReference 256 41 | #define cJSON_StringIsConst 512 42 | 43 | /* The cJSON structure: */ 44 | typedef struct cJSON 45 | { 46 | /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ 47 | struct cJSON *next; 48 | struct cJSON *prev; 49 | /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ 50 | struct cJSON *child; 51 | 52 | /* The type of the item, as above. */ 53 | int type; 54 | 55 | /* The item's string, if type==cJSON_String */ 56 | char *valuestring; 57 | /* The item's number, if type==cJSON_Number */ 58 | int valueint; 59 | /* The item's number, if type==cJSON_Number */ 60 | double valuedouble; 61 | 62 | /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 63 | char *string; 64 | } cJSON; 65 | 66 | typedef struct cJSON_Hooks 67 | { 68 | void *(*malloc_fn)(size_t sz); 69 | void (*free_fn)(void *ptr); 70 | } cJSON_Hooks; 71 | 72 | /* Supply malloc, realloc and free functions to cJSON */ 73 | extern void cJSON_InitHooks(cJSON_Hooks* hooks); 74 | 75 | 76 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ 77 | extern cJSON *cJSON_Parse(const char *value); 78 | /* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ 79 | extern char *cJSON_Print(const cJSON *item); 80 | /* No need to free. Limit to 100000 */ 81 | extern char *cJSON_Print_static(const cJSON *item); 82 | 83 | /* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ 84 | extern char *cJSON_PrintUnformatted(const cJSON *item); 85 | /* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ 86 | extern char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, int fmt); 87 | /* Delete a cJSON entity and all subentities. */ 88 | extern void cJSON_Delete(cJSON *c); 89 | 90 | /* Returns the number of items in an array (or object). */ 91 | extern int cJSON_GetArraySize(const cJSON *array); 92 | /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ 93 | extern cJSON *cJSON_GetArrayItem(const cJSON *array, int item); 94 | 95 | /* Retrieve item string value. Returns NULL if unsuccessful. */ 96 | char *cJSON_GetObjectString(const cJSON *object, const char *string); 97 | 98 | /* Get item "string" from object. Case insensitive. */ 99 | extern cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string); 100 | extern int cJSON_HasObjectItem(const cJSON *object, const char *string); 101 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 102 | extern const char *cJSON_GetErrorPtr(void); 103 | 104 | /* These calls create a cJSON item of the appropriate type. */ 105 | extern cJSON *cJSON_CreateNull(void); 106 | extern cJSON *cJSON_CreateTrue(void); 107 | extern cJSON *cJSON_CreateFalse(void); 108 | extern cJSON *cJSON_CreateBool(int b); 109 | extern cJSON *cJSON_CreateNumber(double num); 110 | extern cJSON *cJSON_CreateString(const char *string); 111 | extern cJSON *cJSON_CreateArray(void); 112 | extern cJSON *cJSON_CreateObject(void); 113 | 114 | /* These utilities create an Array of count items. */ 115 | extern cJSON *cJSON_CreateIntArray(const int *numbers, int count); 116 | extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count); 117 | extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count); 118 | extern cJSON *cJSON_CreateStringArray(const char **strings, int count); 119 | 120 | /* Append item to the specified array/object. */ 121 | extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); 122 | extern void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); 123 | extern void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object */ 124 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ 125 | extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 126 | extern void cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); 127 | 128 | /* Remove/Detatch items from Arrays/Objects. */ 129 | extern cJSON *cJSON_DetachItemFromArray(cJSON *array, int which); 130 | extern void cJSON_DeleteItemFromArray(cJSON *array, int which); 131 | extern cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string); 132 | extern void cJSON_DeleteItemFromObject(cJSON *object, const char *string); 133 | 134 | /* Update array items. */ 135 | extern void cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ 136 | extern void cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); 137 | extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); 138 | 139 | /* Duplicate a cJSON item */ 140 | extern cJSON *cJSON_Duplicate(const cJSON *item, int recurse); 141 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will 142 | need to be released. With recurse!=0, it will duplicate any children connected to the item. 143 | The item->next and ->prev pointers are always zero on return from Duplicate. */ 144 | 145 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ 146 | /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ 147 | extern cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, int require_null_terminated); 148 | 149 | extern void cJSON_Minify(char *json); 150 | 151 | /* Macros for creating things quickly. */ 152 | #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) 153 | #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) 154 | #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) 155 | #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) 156 | #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) 157 | #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) 158 | 159 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */ 160 | #define cJSON_SetIntValue(object,val) ((object) ? (object)->valueint = (object)->valuedouble = (val) : (val)) 161 | #define cJSON_SetNumberValue(object,val) ((object) ? (object)->valueint = (object)->valuedouble = (val) : (val)) 162 | 163 | /* Macro for iterating over an array */ 164 | #define cJSON_ArrayForEach(pos, head) for(pos = (head)->child; pos != NULL; pos = pos->next) 165 | 166 | #ifdef __cplusplus 167 | } 168 | #endif 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /cJSON/cJSON_Utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "cJSON_Utils.h" 6 | 7 | static int cJSONUtils_strcasecmp(const char *s1,const char *s2) 8 | { 9 | if (!s1) return (s1==s2)?0:1;if (!s2) return 1; 10 | for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0; 11 | return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); 12 | } 13 | 14 | /* JSON Pointer implementation: */ 15 | static int cJSONUtils_Pstrcasecmp(const char *a,const char *e) 16 | { 17 | if (!a || !e) return (a==e)?0:1; 18 | for (;*a && *e && *e!='/';a++,e++) { 19 | if (*e=='~') {if (!(e[1]=='0' && *a=='~') && !(e[1]=='1' && *a=='/')) return 1; else e++;} 20 | else if (tolower(*a)!=tolower(*e)) return 1; 21 | } 22 | if ((*e!=0 && *e!='/') != (*a!=0)) return 1; 23 | return 0; 24 | } 25 | 26 | static int cJSONUtils_PointerEncodedstrlen(const char *s) {int l=0;for (;*s;s++,l++) if (*s=='~' || *s=='/') l++;return l;} 27 | 28 | static void cJSONUtils_PointerEncodedstrcpy(char *d,const char *s) 29 | { 30 | for (;*s;s++) 31 | { 32 | if (*s=='/') {*d++='~';*d++='1';} 33 | else if (*s=='~') {*d++='~';*d++='0';} 34 | else *d++=*s; 35 | } 36 | *d=0; 37 | } 38 | 39 | char *cJSONUtils_FindPointerFromObjectTo(cJSON *object,cJSON *target) 40 | { 41 | int type=object->type,c=0;cJSON *obj=0; 42 | 43 | if (object==target) return strdup(""); 44 | 45 | for (obj=object->child;obj;obj=obj->next,c++) 46 | { 47 | char *found=cJSONUtils_FindPointerFromObjectTo(obj,target); 48 | if (found) 49 | { 50 | if (type==cJSON_Array) 51 | { 52 | char *ret=(char*)malloc(strlen(found)+23); 53 | sprintf(ret,"/%d%s",c,found); 54 | free(found); 55 | return ret; 56 | } 57 | else if (type==cJSON_Object) 58 | { 59 | char *ret=(char*)malloc(strlen(found)+cJSONUtils_PointerEncodedstrlen(obj->string)+2); 60 | *ret='/';cJSONUtils_PointerEncodedstrcpy(ret+1,obj->string); 61 | strcat(ret,found); 62 | free(found); 63 | return ret; 64 | } 65 | free(found); 66 | return 0; 67 | } 68 | } 69 | return 0; 70 | } 71 | 72 | cJSON *cJSONUtils_GetPointer(cJSON *object,const char *pointer) 73 | { 74 | while (*pointer++=='/' && object) 75 | { 76 | if (object->type==cJSON_Array) 77 | { 78 | int which=0; while (*pointer>='0' && *pointer<='9') which=(10*which) + *pointer++ - '0'; 79 | if (*pointer && *pointer!='/') return 0; 80 | object=cJSON_GetArrayItem(object,which); 81 | } 82 | else if (object->type==cJSON_Object) 83 | { 84 | object=object->child; while (object && cJSONUtils_Pstrcasecmp(object->string,pointer)) object=object->next; /* GetObjectItem. */ 85 | while (*pointer && *pointer!='/') pointer++; 86 | } 87 | else return 0; 88 | } 89 | return object; 90 | } 91 | 92 | /* JSON Patch implementation. */ 93 | static void cJSONUtils_InplaceDecodePointerString(char *string) 94 | { 95 | char *s2=string; 96 | for (;*string;s2++,string++) *s2=(*string!='~')?(*string):((*(++string)=='0')?'~':'/'); 97 | *s2=0; 98 | } 99 | 100 | static cJSON *cJSONUtils_PatchDetach(cJSON *object,const char *path) 101 | { 102 | char *parentptr=0,*childptr=0;cJSON *parent=0,*ret=0; 103 | 104 | parentptr=strdup(path); childptr=strrchr(parentptr,'/'); if (childptr) *childptr++=0; 105 | parent=cJSONUtils_GetPointer(object,parentptr); 106 | cJSONUtils_InplaceDecodePointerString(childptr); 107 | 108 | if (!parent) ret=0; /* Couldn't find object to remove child from. */ 109 | else if (parent->type==cJSON_Array) ret=cJSON_DetachItemFromArray(parent,atoi(childptr)); 110 | else if (parent->type==cJSON_Object) ret=cJSON_DetachItemFromObject(parent,childptr); 111 | free(parentptr); 112 | return ret; 113 | } 114 | 115 | static int cJSONUtils_Compare(cJSON *a,cJSON *b) 116 | { 117 | if (a->type!=b->type) return -1; /* mismatched type. */ 118 | switch (a->type) 119 | { 120 | case cJSON_Number: return (a->valueint!=b->valueint || a->valuedouble!=b->valuedouble)?-2:0; /* numeric mismatch. */ 121 | case cJSON_String: return (strcmp(a->valuestring,b->valuestring)!=0)?-3:0; /* string mismatch. */ 122 | case cJSON_Array: for (a=a->child,b=b->child;a && b;a=a->next,b=b->next) {int err=cJSONUtils_Compare(a,b);if (err) return err;} 123 | return (a || b)?-4:0; /* array size mismatch. */ 124 | case cJSON_Object: 125 | cJSONUtils_SortObject(a); 126 | cJSONUtils_SortObject(b); 127 | a=a->child,b=b->child; 128 | while (a && b) 129 | { 130 | int err; 131 | if (cJSONUtils_strcasecmp(a->string,b->string)) return -6; /* missing member */ 132 | err=cJSONUtils_Compare(a,b);if (err) return err; 133 | a=a->next,b=b->next; 134 | } 135 | return (a || b)?-5:0; /* object length mismatch */ 136 | 137 | default: break; 138 | } 139 | return 0; 140 | } 141 | 142 | static int cJSONUtils_ApplyPatch(cJSON *object,cJSON *patch) 143 | { 144 | cJSON *op=0,*path=0,*value=0,*parent=0;int opcode=0;char *parentptr=0,*childptr=0; 145 | 146 | op=cJSON_GetObjectItem(patch,"op"); 147 | path=cJSON_GetObjectItem(patch,"path"); 148 | if (!op || !path) return 2; /* malformed patch. */ 149 | 150 | if (!strcmp(op->valuestring,"add")) opcode=0; 151 | else if (!strcmp(op->valuestring,"remove")) opcode=1; 152 | else if (!strcmp(op->valuestring,"replace"))opcode=2; 153 | else if (!strcmp(op->valuestring,"move")) opcode=3; 154 | else if (!strcmp(op->valuestring,"copy")) opcode=4; 155 | else if (!strcmp(op->valuestring,"test")) return cJSONUtils_Compare(cJSONUtils_GetPointer(object,path->valuestring),cJSON_GetObjectItem(patch,"value")); 156 | else return 3; /* unknown opcode. */ 157 | 158 | if (opcode==1 || opcode==2) /* Remove/Replace */ 159 | { 160 | cJSON_Delete(cJSONUtils_PatchDetach(object,path->valuestring)); /* Get rid of old. */ 161 | if (opcode==1) return 0; /* For Remove, this is job done. */ 162 | } 163 | 164 | if (opcode==3 || opcode==4) /* Copy/Move uses "from". */ 165 | { 166 | cJSON *from=cJSON_GetObjectItem(patch,"from"); if (!from) return 4; /* missing "from" for copy/move. */ 167 | 168 | if (opcode==3) value=cJSONUtils_PatchDetach(object,from->valuestring); 169 | if (opcode==4) value=cJSONUtils_GetPointer(object,from->valuestring); 170 | if (!value) return 5; /* missing "from" for copy/move. */ 171 | if (opcode==4) value=cJSON_Duplicate(value,1); 172 | if (!value) return 6; /* out of memory for copy/move. */ 173 | } 174 | else /* Add/Replace uses "value". */ 175 | { 176 | value=cJSON_GetObjectItem(patch,"value"); 177 | if (!value) return 7; /* missing "value" for add/replace. */ 178 | value=cJSON_Duplicate(value,1); 179 | if (!value) return 8; /* out of memory for add/replace. */ 180 | } 181 | 182 | /* Now, just add "value" to "path". */ 183 | 184 | parentptr=strdup(path->valuestring); childptr=strrchr(parentptr,'/'); if (childptr) *childptr++=0; 185 | parent=cJSONUtils_GetPointer(object,parentptr); 186 | cJSONUtils_InplaceDecodePointerString(childptr); 187 | 188 | /* add, remove, replace, move, copy, test. */ 189 | if (!parent) {free(parentptr); cJSON_Delete(value); return 9;} /* Couldn't find object to add to. */ 190 | else if (parent->type==cJSON_Array) 191 | { 192 | if (!strcmp(childptr,"-")) cJSON_AddItemToArray(parent,value); 193 | else cJSON_InsertItemInArray(parent,atoi(childptr),value); 194 | } 195 | else if (parent->type==cJSON_Object) 196 | { 197 | cJSON_DeleteItemFromObject(parent,childptr); 198 | cJSON_AddItemToObject(parent,childptr,value); 199 | } 200 | else 201 | { 202 | cJSON_Delete(value); 203 | } 204 | free(parentptr); 205 | return 0; 206 | } 207 | 208 | 209 | int cJSONUtils_ApplyPatches(cJSON *object,cJSON *patches) 210 | { 211 | int err; 212 | if (patches->type!=cJSON_Array) return 1; /* malformed patches. */ 213 | if (patches) patches=patches->child; 214 | while (patches) 215 | { 216 | if ((err=cJSONUtils_ApplyPatch(object,patches))) return err; 217 | patches=patches->next; 218 | } 219 | return 0; 220 | } 221 | 222 | static void cJSONUtils_GeneratePatch(cJSON *patches,const char *op,const char *path,const char *suffix,cJSON *val) 223 | { 224 | cJSON *patch=cJSON_CreateObject(); 225 | cJSON_AddItemToObject(patch,"op",cJSON_CreateString(op)); 226 | if (suffix) 227 | { 228 | char *newpath=(char*)malloc(strlen(path)+cJSONUtils_PointerEncodedstrlen(suffix)+2); 229 | cJSONUtils_PointerEncodedstrcpy(newpath+sprintf(newpath,"%s/",path),suffix); 230 | cJSON_AddItemToObject(patch,"path",cJSON_CreateString(newpath)); 231 | free(newpath); 232 | } 233 | else cJSON_AddItemToObject(patch,"path",cJSON_CreateString(path)); 234 | if (val) cJSON_AddItemToObject(patch,"value",cJSON_Duplicate(val,1)); 235 | cJSON_AddItemToArray(patches,patch); 236 | } 237 | 238 | void cJSONUtils_AddPatchToArray(cJSON *array,const char *op,const char *path,cJSON *val) {cJSONUtils_GeneratePatch(array,op,path,0,val);} 239 | 240 | static void cJSONUtils_CompareToPatch(cJSON *patches,const char *path,cJSON *from,cJSON *to) 241 | { 242 | if (from->type!=to->type) {cJSONUtils_GeneratePatch(patches,"replace",path,0,to); return; } 243 | 244 | switch (from->type) 245 | { 246 | case cJSON_Number: 247 | if (from->valueint!=to->valueint || from->valuedouble!=to->valuedouble) 248 | cJSONUtils_GeneratePatch(patches,"replace",path,0,to); 249 | return; 250 | 251 | case cJSON_String: 252 | if (strcmp(from->valuestring,to->valuestring)!=0) 253 | cJSONUtils_GeneratePatch(patches,"replace",path,0,to); 254 | return; 255 | 256 | case cJSON_Array: 257 | { 258 | int c;char *newpath=(char*)malloc(strlen(path)+23); /* Allow space for 64bit int. */ 259 | for (c=0,from=from->child,to=to->child;from && to;from=from->next,to=to->next,c++){ 260 | sprintf(newpath,"%s/%d",path,c); cJSONUtils_CompareToPatch(patches,newpath,from,to); 261 | } 262 | for (;from;from=from->next,c++) {sprintf(newpath,"%d",c); cJSONUtils_GeneratePatch(patches,"remove",path,newpath,0); } 263 | for (;to;to=to->next,c++) cJSONUtils_GeneratePatch(patches,"add",path,"-",to); 264 | free(newpath); 265 | return; 266 | } 267 | 268 | case cJSON_Object: 269 | { 270 | cJSON *a,*b; 271 | cJSONUtils_SortObject(from); 272 | cJSONUtils_SortObject(to); 273 | 274 | a=from->child,b=to->child; 275 | while (a || b) 276 | { 277 | int diff=(!a)?1:(!b)?-1:cJSONUtils_strcasecmp(a->string,b->string); 278 | if (!diff) 279 | { 280 | char *newpath=(char*)malloc(strlen(path)+cJSONUtils_PointerEncodedstrlen(a->string)+2); 281 | cJSONUtils_PointerEncodedstrcpy(newpath+sprintf(newpath,"%s/",path),a->string); 282 | cJSONUtils_CompareToPatch(patches,newpath,a,b); 283 | free(newpath); 284 | a=a->next; 285 | b=b->next; 286 | } 287 | else if (diff<0) {cJSONUtils_GeneratePatch(patches,"remove",path,a->string,0); a=a->next;} 288 | else {cJSONUtils_GeneratePatch(patches,"add",path,b->string,b); b=b->next;} 289 | } 290 | return; 291 | } 292 | 293 | default: break; 294 | } 295 | } 296 | 297 | 298 | cJSON* cJSONUtils_GeneratePatches(cJSON *from,cJSON *to) 299 | { 300 | cJSON *patches=cJSON_CreateArray(); 301 | cJSONUtils_CompareToPatch(patches,"",from,to); 302 | return patches; 303 | } 304 | 305 | 306 | static cJSON *cJSONUtils_SortList(cJSON *list) 307 | { 308 | cJSON *first=list,*second=list,*ptr=list; 309 | 310 | if (!list || !list->next) return list; /* One entry is sorted already. */ 311 | 312 | while (ptr && ptr->next && cJSONUtils_strcasecmp(ptr->string,ptr->next->string)<0) ptr=ptr->next; /* Test for list sorted. */ 313 | if (!ptr || !ptr->next) return list; /* Leave sorted lists unmodified. */ 314 | ptr=list; 315 | 316 | while (ptr) {second=second->next;ptr=ptr->next;if (ptr) ptr=ptr->next;} /* Walk two pointers to find the middle. */ 317 | if (second && second->prev) second->prev->next=0; /* Split the lists */ 318 | 319 | first=cJSONUtils_SortList(first); /* Recursively sort the sub-lists. */ 320 | second=cJSONUtils_SortList(second); 321 | list=ptr=0; 322 | 323 | while (first && second) /* Merge the sub-lists */ 324 | { 325 | if (cJSONUtils_strcasecmp(first->string,second->string)<0) 326 | { 327 | if (!list) list=ptr=first; 328 | else {ptr->next=first;first->prev=ptr;ptr=first;} 329 | first=first->next; 330 | } 331 | else 332 | { 333 | if (!list) list=ptr=second; 334 | else {ptr->next=second;second->prev=ptr;ptr=second;} 335 | second=second->next; 336 | } 337 | } 338 | if (first) { if (!list) return first; ptr->next=first; first->prev=ptr; } /* Append any tails. */ 339 | if (second) { if (!list) return second; ptr->next=second; second->prev=ptr; } 340 | 341 | return list; 342 | } 343 | 344 | void cJSONUtils_SortObject(cJSON *object) {object->child=cJSONUtils_SortList(object->child);} 345 | 346 | cJSON* cJSONUtils_MergePatch(cJSON *target, cJSON *patch) 347 | { 348 | if (!patch || patch->type != cJSON_Object) {cJSON_Delete(target);return cJSON_Duplicate(patch,1);} 349 | if (!target || target->type != cJSON_Object) {cJSON_Delete(target);target=cJSON_CreateObject();} 350 | 351 | patch=patch->child; 352 | while (patch) 353 | { 354 | if (patch->type == cJSON_NULL) cJSON_DeleteItemFromObject(target,patch->string); 355 | else 356 | { 357 | cJSON *replaceme=cJSON_DetachItemFromObject(target,patch->string); 358 | cJSON_AddItemToObject(target,patch->string,cJSONUtils_MergePatch(replaceme,patch)); 359 | } 360 | patch=patch->next; 361 | } 362 | return target; 363 | } 364 | 365 | cJSON *cJSONUtils_GenerateMergePatch(cJSON *from,cJSON *to) 366 | { 367 | cJSON *patch=0; 368 | if (!to) return cJSON_CreateNull(); 369 | if (to->type!=cJSON_Object || !from || from->type!=cJSON_Object) return cJSON_Duplicate(to,1); 370 | cJSONUtils_SortObject(from); 371 | cJSONUtils_SortObject(to); 372 | from=from->child;to=to->child; 373 | patch=cJSON_CreateObject(); 374 | while (from || to) 375 | { 376 | int compare=from?(to?strcmp(from->string,to->string):-1):1; 377 | if (compare<0) 378 | { 379 | cJSON_AddItemToObject(patch,from->string,cJSON_CreateNull()); 380 | from=from->next; 381 | } 382 | else if (compare>0) 383 | { 384 | cJSON_AddItemToObject(patch,to->string,cJSON_Duplicate(to,1)); 385 | to=to->next; 386 | } 387 | else 388 | { 389 | if (cJSONUtils_Compare(from,to)) cJSON_AddItemToObject(patch,to->string,cJSONUtils_GenerateMergePatch(from,to)); 390 | from=from->next;to=to->next; 391 | } 392 | } 393 | if (!patch->child) {cJSON_Delete(patch);return 0;} 394 | return patch; 395 | } -------------------------------------------------------------------------------- /cJSON/cJSON_Utils.h: -------------------------------------------------------------------------------- 1 | #include "cJSON.h" 2 | 3 | /* Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec. */ 4 | cJSON *cJSONUtils_GetPointer(cJSON *object,const char *pointer); 5 | 6 | /* Implement RFC6902 (https://tools.ietf.org/html/rfc6902) JSON Patch spec. */ 7 | cJSON* cJSONUtils_GeneratePatches(cJSON *from,cJSON *to); 8 | void cJSONUtils_AddPatchToArray(cJSON *array,const char *op,const char *path,cJSON *val); /* Utility for generating patch array entries. */ 9 | int cJSONUtils_ApplyPatches(cJSON *object,cJSON *patches); /* Returns 0 for success. */ 10 | 11 | /* 12 | // Note that ApplyPatches is NOT atomic on failure. To implement an atomic ApplyPatches, use: 13 | //int cJSONUtils_AtomicApplyPatches(cJSON **object, cJSON *patches) 14 | //{ 15 | // cJSON *modme=cJSON_Duplicate(*object,1); 16 | // int error=cJSONUtils_ApplyPatches(modme,patches); 17 | // if (!error) {cJSON_Delete(*object);*object=modme;} 18 | // else cJSON_Delete(modme); 19 | // return error; 20 | //} 21 | // Code not added to library since this strategy is a LOT slower. 22 | */ 23 | 24 | /* Implement RFC7386 (https://tools.ietf.org/html/rfc7396) JSON Merge Patch spec. */ 25 | cJSON* cJSONUtils_MergePatch(cJSON *target, cJSON *patch); /* target will be modified by patch. return value is new ptr for target. */ 26 | cJSON *cJSONUtils_GenerateMergePatch(cJSON *from,cJSON *to); /* generates a patch to move from -> to */ 27 | 28 | char *cJSONUtils_FindPointerFromObjectTo(cJSON *object,cJSON *target); /* Given a root object and a target object, construct a pointer from one to the other. */ 29 | 30 | void cJSONUtils_SortObject(cJSON *object); /* Sorts the members of the object into alphabetical order. */ 31 | -------------------------------------------------------------------------------- /cJSON/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "cJSON.h" 26 | 27 | /* Parse text to JSON, then render back to text, and print! */ 28 | void doit(char *text) 29 | { 30 | char *out;cJSON *json; 31 | 32 | json=cJSON_Parse(text); 33 | if (!json) {printf("Error before: [%s]\n",cJSON_GetErrorPtr());} 34 | else 35 | { 36 | out=cJSON_Print(json); 37 | cJSON_Delete(json); 38 | printf("%s\n",out); 39 | free(out); 40 | } 41 | } 42 | 43 | /* Read a file, parse, render back, etc. */ 44 | void dofile(char *filename) 45 | { 46 | FILE *f;long len;char *data; 47 | 48 | f=fopen(filename,"rb");fseek(f,0,SEEK_END);len=ftell(f);fseek(f,0,SEEK_SET); 49 | data=(char*)malloc(len+1);fread(data,1,len,f);data[len]='\0';fclose(f); 50 | doit(data); 51 | free(data); 52 | } 53 | 54 | /* Used by some code below as an example datatype. */ 55 | struct record {const char *precision;double lat,lon;const char *address,*city,*state,*zip,*country; }; 56 | 57 | /* Create a bunch of objects as demonstration. */ 58 | void create_objects() 59 | { 60 | cJSON *root,*fmt,*img,*thm,*fld;char *out;int i; /* declare a few. */ 61 | /* Our "days of the week" array: */ 62 | const char *strings[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; 63 | /* Our matrix: */ 64 | int numbers[3][3]={{0,-1,0},{1,0,0},{0,0,1}}; 65 | /* Our "gallery" item: */ 66 | int ids[4]={116,943,234,38793}; 67 | /* Our array of "records": */ 68 | struct record fields[2]={ 69 | {"zip",37.7668,-1.223959e+2,"","SAN FRANCISCO","CA","94107","US"}, 70 | {"zip",37.371991,-1.22026e+2,"","SUNNYVALE","CA","94085","US"}}; 71 | volatile double zero = 0.0; 72 | 73 | /* Here we construct some JSON standards, from the JSON site. */ 74 | 75 | /* Our "Video" datatype: */ 76 | root=cJSON_CreateObject(); 77 | cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble")); 78 | cJSON_AddItemToObject(root, "format", fmt=cJSON_CreateObject()); 79 | cJSON_AddStringToObject(fmt,"type", "rect"); 80 | cJSON_AddNumberToObject(fmt,"width", 1920); 81 | cJSON_AddNumberToObject(fmt,"height", 1080); 82 | cJSON_AddFalseToObject (fmt,"interlace"); 83 | cJSON_AddNumberToObject(fmt,"frame rate", 24); 84 | 85 | out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); /* Print to text, Delete the cJSON, print it, release the string. */ 86 | 87 | /* Our "days of the week" array: */ 88 | root=cJSON_CreateStringArray(strings,7); 89 | 90 | out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); 91 | 92 | /* Our matrix: */ 93 | root=cJSON_CreateArray(); 94 | for (i=0;i<3;i++) cJSON_AddItemToArray(root,cJSON_CreateIntArray(numbers[i],3)); 95 | 96 | /* cJSON_ReplaceItemInArray(root,1,cJSON_CreateString("Replacement")); */ 97 | 98 | out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); 99 | 100 | 101 | /* Our "gallery" item: */ 102 | root=cJSON_CreateObject(); 103 | cJSON_AddItemToObject(root, "Image", img=cJSON_CreateObject()); 104 | cJSON_AddNumberToObject(img,"Width",800); 105 | cJSON_AddNumberToObject(img,"Height",600); 106 | cJSON_AddStringToObject(img,"Title","View from 15th Floor"); 107 | cJSON_AddItemToObject(img, "Thumbnail", thm=cJSON_CreateObject()); 108 | cJSON_AddStringToObject(thm, "Url", "http:/*www.example.com/image/481989943"); 109 | cJSON_AddNumberToObject(thm,"Height",125); 110 | cJSON_AddStringToObject(thm,"Width","100"); 111 | cJSON_AddItemToObject(img,"IDs", cJSON_CreateIntArray(ids,4)); 112 | 113 | out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); 114 | 115 | /* Our array of "records": */ 116 | 117 | root=cJSON_CreateArray(); 118 | for (i=0;i<2;i++) 119 | { 120 | cJSON_AddItemToArray(root,fld=cJSON_CreateObject()); 121 | cJSON_AddStringToObject(fld, "precision", fields[i].precision); 122 | cJSON_AddNumberToObject(fld, "Latitude", fields[i].lat); 123 | cJSON_AddNumberToObject(fld, "Longitude", fields[i].lon); 124 | cJSON_AddStringToObject(fld, "Address", fields[i].address); 125 | cJSON_AddStringToObject(fld, "City", fields[i].city); 126 | cJSON_AddStringToObject(fld, "State", fields[i].state); 127 | cJSON_AddStringToObject(fld, "Zip", fields[i].zip); 128 | cJSON_AddStringToObject(fld, "Country", fields[i].country); 129 | } 130 | 131 | /* cJSON_ReplaceItemInObject(cJSON_GetArrayItem(root,1),"City",cJSON_CreateIntArray(ids,4)); */ 132 | 133 | out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); 134 | 135 | root=cJSON_CreateObject(); 136 | cJSON_AddNumberToObject(root,"number", 1.0/zero); 137 | out=cJSON_Print(root); cJSON_Delete(root); printf("%s\n",out); free(out); 138 | } 139 | 140 | int main (int argc, const char * argv[]) { 141 | /* a bunch of json: */ 142 | char text1[]="{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\", \n\"format\": {\"type\": \"rect\", \n\"width\": 1920, \n\"height\": 1080, \n\"interlace\": false,\"frame rate\": 24\n}\n}"; 143 | char text2[]="[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]"; 144 | char text3[]="[\n [0, -1, 0],\n [1, 0, 0],\n [0, 0, 1]\n ]\n"; 145 | char text4[]="{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http:/*www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": \"100\"\n },\n \"IDs\": [116, 943, 234, 38793]\n }\n }"; 146 | char text5[]="[\n {\n \"precision\": \"zip\",\n \"Latitude\": 37.7668,\n \"Longitude\": -122.3959,\n \"Address\": \"\",\n \"City\": \"SAN FRANCISCO\",\n \"State\": \"CA\",\n \"Zip\": \"94107\",\n \"Country\": \"US\"\n },\n {\n \"precision\": \"zip\",\n \"Latitude\": 37.371991,\n \"Longitude\": -122.026020,\n \"Address\": \"\",\n \"City\": \"SUNNYVALE\",\n \"State\": \"CA\",\n \"Zip\": \"94085\",\n \"Country\": \"US\"\n }\n ]"; 147 | 148 | char text6[] = "" 149 | "\n" 150 | "\n" 151 | " \n" 152 | " \n" 156 | "Application Error\n" 157 | "\n" 158 | "\n" 159 | " \n" 162 | "\n" 163 | "\n"; 164 | 165 | /* Process each json textblock by parsing, then rebuilding: */ 166 | doit(text1); 167 | doit(text2); 168 | doit(text3); 169 | doit(text4); 170 | doit(text5); 171 | doit(text6); 172 | 173 | /* Parse standard testfiles: */ 174 | /* dofile("../../tests/test1"); */ 175 | /* dofile("../../tests/test2"); */ 176 | /* dofile("../../tests/test3"); */ 177 | /* dofile("../../tests/test4"); */ 178 | /* dofile("../../tests/test5"); */ 179 | /* dofile("../../tests/test6"); */ 180 | 181 | /* Now some samplecode for building objects concisely: */ 182 | create_objects(); 183 | 184 | return 0; 185 | } 186 | -------------------------------------------------------------------------------- /cJSON/test_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "cJSON_Utils.h" 5 | 6 | int main() 7 | { 8 | int i; 9 | /* JSON Pointer tests: */ 10 | cJSON *root; 11 | const char *json="{" 12 | "\"foo\": [\"bar\", \"baz\"]," 13 | "\"\": 0," 14 | "\"a/b\": 1," 15 | "\"c%d\": 2," 16 | "\"e^f\": 3," 17 | "\"g|h\": 4," 18 | "\"i\\\\j\": 5," 19 | "\"k\\\"l\": 6," 20 | "\" \": 7," 21 | "\"m~n\": 8" 22 | "}"; 23 | 24 | const char *tests[12]={"","/foo","/foo/0","/","/a~1b","/c%d","/e^f","/g|h","/i\\j","/k\"l","/ ","/m~0n"}; 25 | 26 | /* JSON Apply Patch tests: */ 27 | const char *patches[15][3]={ 28 | {"{ \"foo\": \"bar\"}", "[{ \"op\": \"add\", \"path\": \"/baz\", \"value\": \"qux\" }]","{\"baz\": \"qux\",\"foo\": \"bar\"}"}, 29 | {"{ \"foo\": [ \"bar\", \"baz\" ] }", "[{ \"op\": \"add\", \"path\": \"/foo/1\", \"value\": \"qux\" }]","{\"foo\": [ \"bar\", \"qux\", \"baz\" ] }"}, 30 | {"{\"baz\": \"qux\",\"foo\": \"bar\"}"," [{ \"op\": \"remove\", \"path\": \"/baz\" }]","{\"foo\": \"bar\" }"}, 31 | {"{ \"foo\": [ \"bar\", \"qux\", \"baz\" ] }","[{ \"op\": \"remove\", \"path\": \"/foo/1\" }]","{\"foo\": [ \"bar\", \"baz\" ] }"}, 32 | {"{ \"baz\": \"qux\",\"foo\": \"bar\"}","[{ \"op\": \"replace\", \"path\": \"/baz\", \"value\": \"boo\" }]","{\"baz\": \"boo\",\"foo\": \"bar\"}"}, 33 | {"{\"foo\": {\"bar\": \"baz\",\"waldo\": \"fred\"},\"qux\": {\"corge\": \"grault\"}}","[{ \"op\": \"move\", \"from\": \"/foo/waldo\", \"path\": \"/qux/thud\" }]","{\"foo\": {\"bar\": \"baz\"},\"qux\": {\"corge\": \"grault\",\"thud\": \"fred\"}}"}, 34 | {"{ \"foo\": [ \"all\", \"grass\", \"cows\", \"eat\" ] }","[ { \"op\": \"move\", \"from\": \"/foo/1\", \"path\": \"/foo/3\" }]","{ \"foo\": [ \"all\", \"cows\", \"eat\", \"grass\" ] }"}, 35 | {"{\"baz\": \"qux\",\"foo\": [ \"a\", 2, \"c\" ]}","[{ \"op\": \"test\", \"path\": \"/baz\", \"value\": \"qux\" },{ \"op\": \"test\", \"path\": \"/foo/1\", \"value\": 2 }]",""}, 36 | {"{ \"baz\": \"qux\" }","[ { \"op\": \"test\", \"path\": \"/baz\", \"value\": \"bar\" }]",""}, 37 | {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/child\", \"value\": { \"grandchild\": { } } }]","{\"foo\": \"bar\",\"child\": {\"grandchild\": {}}}"}, 38 | {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/baz\", \"value\": \"qux\", \"xyz\": 123 }]","{\"foo\": \"bar\",\"baz\": \"qux\"}"}, 39 | {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/baz/bat\", \"value\": \"qux\" }]",""}, 40 | {"{\"/\": 9,\"~1\": 10}","[{\"op\": \"test\", \"path\": \"/~01\", \"value\": 10}]",""}, 41 | {"{\"/\": 9,\"~1\": 10}","[{\"op\": \"test\", \"path\": \"/~01\", \"value\": \"10\"}]",""}, 42 | {"{ \"foo\": [\"bar\"] }","[ { \"op\": \"add\", \"path\": \"/foo/-\", \"value\": [\"abc\", \"def\"] }]","{\"foo\": [\"bar\", [\"abc\", \"def\"]] }"}}; 43 | 44 | /* JSON Apply Merge tests: */ 45 | const char *merges[15][3]={ 46 | {"{\"a\":\"b\"}", "{\"a\":\"c\"}", "{\"a\":\"c\"}"}, 47 | {"{\"a\":\"b\"}", "{\"b\":\"c\"}", "{\"a\":\"b\",\"b\":\"c\"}"}, 48 | {"{\"a\":\"b\"}", "{\"a\":null}", "{}"}, 49 | {"{\"a\":\"b\",\"b\":\"c\"}", "{\"a\":null}", "{\"b\":\"c\"}"}, 50 | {"{\"a\":[\"b\"]}", "{\"a\":\"c\"}", "{\"a\":\"c\"}"}, 51 | {"{\"a\":\"c\"}", "{\"a\":[\"b\"]}", "{\"a\":[\"b\"]}"}, 52 | {"{\"a\":{\"b\":\"c\"}}", "{\"a\":{\"b\":\"d\",\"c\":null}}", "{\"a\":{\"b\":\"d\"}}"}, 53 | {"{\"a\":[{\"b\":\"c\"}]}", "{\"a\":[1]}", "{\"a\":[1]}"}, 54 | {"[\"a\",\"b\"]", "[\"c\",\"d\"]", "[\"c\",\"d\"]"}, 55 | {"{\"a\":\"b\"}", "[\"c\"]", "[\"c\"]"}, 56 | {"{\"a\":\"foo\"}", "null", "null"}, 57 | {"{\"a\":\"foo\"}", "\"bar\"", "\"bar\""}, 58 | {"{\"e\":null}", "{\"a\":1}", "{\"e\":null,\"a\":1}"}, 59 | {"[1,2]", "{\"a\":\"b\",\"c\":null}", "{\"a\":\"b\"}"}, 60 | {"{}","{\"a\":{\"bb\":{\"ccc\":null}}}", "{\"a\":{\"bb\":{}}}"}}; 61 | 62 | 63 | /* Misc tests */ 64 | int numbers[10]={0,1,2,3,4,5,6,7,8,9}; 65 | const char *random="QWERTYUIOPASDFGHJKLZXCVBNM"; 66 | char buf[2]={0,0},*before,*after; 67 | cJSON *object,*nums,*num6,*sortme; 68 | 69 | 70 | 71 | printf("JSON Pointer Tests\n"); 72 | root=cJSON_Parse(json); 73 | for (i=0;i<12;i++) 74 | { 75 | char *output=cJSON_Print(cJSONUtils_GetPointer(root,tests[i])); 76 | printf("Test %d:\n%s\n\n",i+1,output); 77 | free(output); 78 | } 79 | cJSON_Delete(root); 80 | 81 | 82 | printf("JSON Apply Patch Tests\n"); 83 | for (i=0;i<15;i++) 84 | { 85 | cJSON *object=cJSON_Parse(patches[i][0]); 86 | cJSON *patch=cJSON_Parse(patches[i][1]); 87 | int err=cJSONUtils_ApplyPatches(object,patch); 88 | char *output=cJSON_Print(object); 89 | printf("Test %d (err %d):\n%s\n\n",i+1,err,output); 90 | free(output);cJSON_Delete(object);cJSON_Delete(patch); 91 | } 92 | 93 | /* JSON Generate Patch tests: */ 94 | printf("JSON Generate Patch Tests\n"); 95 | for (i=0;i<15;i++) 96 | { 97 | cJSON *from,*to,*patch;char *out; 98 | if (!strlen(patches[i][2])) continue; 99 | from=cJSON_Parse(patches[i][0]); 100 | to=cJSON_Parse(patches[i][2]); 101 | patch=cJSONUtils_GeneratePatches(from,to); 102 | out=cJSON_Print(patch); 103 | printf("Test %d: (patch: %s):\n%s\n\n",i+1,patches[i][1],out); 104 | free(out);cJSON_Delete(from);cJSON_Delete(to);cJSON_Delete(patch); 105 | } 106 | 107 | /* Misc tests: */ 108 | printf("JSON Pointer construct\n"); 109 | object=cJSON_CreateObject(); 110 | nums=cJSON_CreateIntArray(numbers,10); 111 | num6=cJSON_GetArrayItem(nums,6); 112 | cJSON_AddItemToObject(object,"numbers",nums); 113 | char *temp=cJSONUtils_FindPointerFromObjectTo(object,num6); 114 | printf("Pointer: [%s]\n",temp); 115 | free(temp); 116 | temp=cJSONUtils_FindPointerFromObjectTo(object,nums); 117 | printf("Pointer: [%s]\n",temp); 118 | free(temp); 119 | temp=cJSONUtils_FindPointerFromObjectTo(object,object); 120 | printf("Pointer: [%s]\n",temp); 121 | free(temp); 122 | cJSON_Delete(object); 123 | 124 | /* JSON Sort test: */ 125 | sortme=cJSON_CreateObject(); 126 | for (i=0;i<26;i++) 127 | { 128 | buf[0]=random[i];cJSON_AddItemToObject(sortme,buf,cJSON_CreateNumber(1)); 129 | } 130 | before=cJSON_PrintUnformatted(sortme); 131 | cJSONUtils_SortObject(sortme); 132 | after=cJSON_PrintUnformatted(sortme); 133 | printf("Before: [%s]\nAfter: [%s]\n\n",before,after); 134 | free(before);free(after);cJSON_Delete(sortme); 135 | 136 | /* Merge tests: */ 137 | printf("JSON Merge Patch tests\n"); 138 | for (i=0;i<15;i++) 139 | { 140 | cJSON *object=cJSON_Parse(merges[i][0]); 141 | cJSON *patch=cJSON_Parse(merges[i][1]); 142 | char *before=cJSON_PrintUnformatted(object); 143 | char *patchtext=cJSON_PrintUnformatted(patch); 144 | printf("Before: [%s] -> [%s] = ",before,patchtext); 145 | object=cJSONUtils_MergePatch(object,patch); 146 | char *after=cJSON_PrintUnformatted(object); 147 | printf("[%s] vs [%s] (%s)\n",after,merges[i][2],strcmp(after,merges[i][2])?"FAIL":"OK"); 148 | 149 | free(before);free(patchtext);free(after);cJSON_Delete(object);cJSON_Delete(patch); 150 | } 151 | 152 | /* Generate Merge tests: */ 153 | for (i=0;i<15;i++) 154 | { 155 | cJSON *from=cJSON_Parse(merges[i][0]); 156 | cJSON *to=cJSON_Parse(merges[i][2]); 157 | cJSON *patch=cJSONUtils_GenerateMergePatch(from,to); 158 | from=cJSONUtils_MergePatch(from,patch); 159 | char *patchtext=cJSON_PrintUnformatted(patch); 160 | char *patchedtext=cJSON_PrintUnformatted(from); 161 | printf("Patch [%s] vs [%s] = [%s] vs [%s] (%s)\n",patchtext,merges[i][1],patchedtext,merges[i][2],strcmp(patchedtext,merges[i][2])?"FAIL":"OK"); 162 | cJSON_Delete(from);cJSON_Delete(to);cJSON_Delete(patch);free(patchtext);free(patchedtext); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /cJSON/tests/test1: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "GlossDiv": { 5 | "title": "S", 6 | "GlossList": { 7 | "GlossEntry": { 8 | "ID": "SGML", 9 | "SortAs": "SGML", 10 | "GlossTerm": "Standard Generalized Markup Language", 11 | "Acronym": "SGML", 12 | "Abbrev": "ISO 8879:1986", 13 | "GlossDef": { 14 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 15 | "GlossSeeAlso": ["GML", "XML"] 16 | }, 17 | "GlossSee": "markup" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cJSON/tests/test2: -------------------------------------------------------------------------------- 1 | {"menu": { 2 | "id": "file", 3 | "value": "File", 4 | "popup": { 5 | "menuitem": [ 6 | {"value": "New", "onclick": "CreateNewDoc()"}, 7 | {"value": "Open", "onclick": "OpenDoc()"}, 8 | {"value": "Close", "onclick": "CloseDoc()"} 9 | ] 10 | } 11 | }} 12 | -------------------------------------------------------------------------------- /cJSON/tests/test3: -------------------------------------------------------------------------------- 1 | {"widget": { 2 | "debug": "on", 3 | "window": { 4 | "title": "Sample Konfabulator Widget", 5 | "name": "main_window", 6 | "width": 500, 7 | "height": 500 8 | }, 9 | "image": { 10 | "src": "Images/Sun.png", 11 | "name": "sun1", 12 | "hOffset": 250, 13 | "vOffset": 250, 14 | "alignment": "center" 15 | }, 16 | "text": { 17 | "data": "Click Here", 18 | "size": 36, 19 | "style": "bold", 20 | "name": "text1", 21 | "hOffset": 250, 22 | "vOffset": 100, 23 | "alignment": "center", 24 | "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" 25 | } 26 | }} -------------------------------------------------------------------------------- /cJSON/tests/test4: -------------------------------------------------------------------------------- 1 | {"web-app": { 2 | "servlet": [ 3 | { 4 | "servlet-name": "cofaxCDS", 5 | "servlet-class": "org.cofax.cds.CDSServlet", 6 | "init-param": { 7 | "configGlossary:installationAt": "Philadelphia, PA", 8 | "configGlossary:adminEmail": "ksm@pobox.com", 9 | "configGlossary:poweredBy": "Cofax", 10 | "configGlossary:poweredByIcon": "/images/cofax.gif", 11 | "configGlossary:staticPath": "/content/static", 12 | "templateProcessorClass": "org.cofax.WysiwygTemplate", 13 | "templateLoaderClass": "org.cofax.FilesTemplateLoader", 14 | "templatePath": "templates", 15 | "templateOverridePath": "", 16 | "defaultListTemplate": "listTemplate.htm", 17 | "defaultFileTemplate": "articleTemplate.htm", 18 | "useJSP": false, 19 | "jspListTemplate": "listTemplate.jsp", 20 | "jspFileTemplate": "articleTemplate.jsp", 21 | "cachePackageTagsTrack": 200, 22 | "cachePackageTagsStore": 200, 23 | "cachePackageTagsRefresh": 60, 24 | "cacheTemplatesTrack": 100, 25 | "cacheTemplatesStore": 50, 26 | "cacheTemplatesRefresh": 15, 27 | "cachePagesTrack": 200, 28 | "cachePagesStore": 100, 29 | "cachePagesRefresh": 10, 30 | "cachePagesDirtyRead": 10, 31 | "searchEngineListTemplate": "forSearchEnginesList.htm", 32 | "searchEngineFileTemplate": "forSearchEngines.htm", 33 | "searchEngineRobotsDb": "WEB-INF/robots.db", 34 | "useDataStore": true, 35 | "dataStoreClass": "org.cofax.SqlDataStore", 36 | "redirectionClass": "org.cofax.SqlRedirection", 37 | "dataStoreName": "cofax", 38 | "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", 39 | "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", 40 | "dataStoreUser": "sa", 41 | "dataStorePassword": "dataStoreTestQuery", 42 | "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", 43 | "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", 44 | "dataStoreInitConns": 10, 45 | "dataStoreMaxConns": 100, 46 | "dataStoreConnUsageLimit": 100, 47 | "dataStoreLogLevel": "debug", 48 | "maxUrlLength": 500}}, 49 | { 50 | "servlet-name": "cofaxEmail", 51 | "servlet-class": "org.cofax.cds.EmailServlet", 52 | "init-param": { 53 | "mailHost": "mail1", 54 | "mailHostOverride": "mail2"}}, 55 | { 56 | "servlet-name": "cofaxAdmin", 57 | "servlet-class": "org.cofax.cds.AdminServlet"}, 58 | 59 | { 60 | "servlet-name": "fileServlet", 61 | "servlet-class": "org.cofax.cds.FileServlet"}, 62 | { 63 | "servlet-name": "cofaxTools", 64 | "servlet-class": "org.cofax.cms.CofaxToolsServlet", 65 | "init-param": { 66 | "templatePath": "toolstemplates/", 67 | "log": 1, 68 | "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", 69 | "logMaxSize": "", 70 | "dataLog": 1, 71 | "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", 72 | "dataLogMaxSize": "", 73 | "removePageCache": "/content/admin/remove?cache=pages&id=", 74 | "removeTemplateCache": "/content/admin/remove?cache=templates&id=", 75 | "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", 76 | "lookInContext": 1, 77 | "adminGroupID": 4, 78 | "betaServer": true}}], 79 | "servlet-mapping": { 80 | "cofaxCDS": "/", 81 | "cofaxEmail": "/cofaxutil/aemail/*", 82 | "cofaxAdmin": "/admin/*", 83 | "fileServlet": "/static/*", 84 | "cofaxTools": "/tools/*"}, 85 | 86 | "taglib": { 87 | "taglib-uri": "cofax.tld", 88 | "taglib-location": "/WEB-INF/tlds/cofax.tld"}}} -------------------------------------------------------------------------------- /cJSON/tests/test5: -------------------------------------------------------------------------------- 1 | {"menu": { 2 | "header": "SVG Viewer", 3 | "items": [ 4 | {"id": "Open"}, 5 | {"id": "OpenNew", "label": "Open New"}, 6 | null, 7 | {"id": "ZoomIn", "label": "Zoom In"}, 8 | {"id": "ZoomOut", "label": "Zoom Out"}, 9 | {"id": "OriginalView", "label": "Original View"}, 10 | null, 11 | {"id": "Quality"}, 12 | {"id": "Pause"}, 13 | {"id": "Mute"}, 14 | null, 15 | {"id": "Find", "label": "Find..."}, 16 | {"id": "FindAgain", "label": "Find Again"}, 17 | {"id": "Copy"}, 18 | {"id": "CopyAgain", "label": "Copy Again"}, 19 | {"id": "CopySVG", "label": "Copy SVG"}, 20 | {"id": "ViewSVG", "label": "View SVG"}, 21 | {"id": "ViewSource", "label": "View Source"}, 22 | {"id": "SaveAs", "label": "Save As"}, 23 | null, 24 | {"id": "Help"}, 25 | {"id": "About", "label": "About Adobe CVG Viewer..."} 26 | ] 27 | }} 28 | -------------------------------------------------------------------------------- /cJSON/tests/test6: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Application Error 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | make clean all && valgrind --suppressions=/mnt/disk/training/redis/redis.my/src/valgrind.sup /mnt/disk/training/redis/redis.my/src/redis-server --loadmodule ./timeseries.so 2 | 3 | gdb -ex=r --args /mnt/disk/training/redis/redis.my/src/redis-server --loadmodule ./timeseries.so -------------------------------------------------------------------------------- /redismodule.h: -------------------------------------------------------------------------------- 1 | #ifndef REDISMODULE_H 2 | #define REDISMODULE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* ---------------- Defines common between core and modules --------------- */ 9 | 10 | /* Error status return values. */ 11 | #define REDISMODULE_OK 0 12 | #define REDISMODULE_ERR 1 13 | 14 | /* API versions. */ 15 | #define REDISMODULE_APIVER_1 1 16 | 17 | /* API flags and constants */ 18 | #define REDISMODULE_READ (1<<0) 19 | #define REDISMODULE_WRITE (1<<1) 20 | 21 | #define REDISMODULE_LIST_HEAD 0 22 | #define REDISMODULE_LIST_TAIL 1 23 | 24 | /* Key types. */ 25 | #define REDISMODULE_KEYTYPE_EMPTY 0 26 | #define REDISMODULE_KEYTYPE_STRING 1 27 | #define REDISMODULE_KEYTYPE_LIST 2 28 | #define REDISMODULE_KEYTYPE_HASH 3 29 | #define REDISMODULE_KEYTYPE_SET 4 30 | #define REDISMODULE_KEYTYPE_ZSET 5 31 | #define REDISMODULE_KEYTYPE_MODULE 6 32 | 33 | /* Reply types. */ 34 | #define REDISMODULE_REPLY_UNKNOWN -1 35 | #define REDISMODULE_REPLY_STRING 0 36 | #define REDISMODULE_REPLY_ERROR 1 37 | #define REDISMODULE_REPLY_INTEGER 2 38 | #define REDISMODULE_REPLY_ARRAY 3 39 | #define REDISMODULE_REPLY_NULL 4 40 | 41 | /* Postponed array length. */ 42 | #define REDISMODULE_POSTPONED_ARRAY_LEN -1 43 | 44 | /* Expire */ 45 | #define REDISMODULE_NO_EXPIRE -1 46 | 47 | /* Sorted set API flags. */ 48 | #define REDISMODULE_ZADD_XX (1<<0) 49 | #define REDISMODULE_ZADD_NX (1<<1) 50 | #define REDISMODULE_ZADD_ADDED (1<<2) 51 | #define REDISMODULE_ZADD_UPDATED (1<<3) 52 | #define REDISMODULE_ZADD_NOP (1<<4) 53 | 54 | /* Hash API flags. */ 55 | #define REDISMODULE_HASH_NONE 0 56 | #define REDISMODULE_HASH_NX (1<<0) 57 | #define REDISMODULE_HASH_XX (1<<1) 58 | #define REDISMODULE_HASH_CFIELDS (1<<2) 59 | #define REDISMODULE_HASH_EXISTS (1<<3) 60 | 61 | /* A special pointer that we can use between the core and the module to signal 62 | * field deletion, and that is impossible to be a valid pointer. */ 63 | #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) 64 | 65 | /* Error messages. */ 66 | #define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value" 67 | 68 | #define REDISMODULE_POSITIVE_INFINITE (1.0/0.0) 69 | #define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0) 70 | 71 | /* ------------------------- End of common defines ------------------------ */ 72 | 73 | #ifndef REDISMODULE_CORE 74 | 75 | typedef long long mstime_t; 76 | 77 | /* Incomplete structures for compiler checks but opaque access. */ 78 | typedef struct RedisModuleCtx RedisModuleCtx; 79 | typedef struct RedisModuleKey RedisModuleKey; 80 | typedef struct RedisModuleString RedisModuleString; 81 | typedef struct RedisModuleCallReply RedisModuleCallReply; 82 | typedef struct RedisModuleIO RedisModuleIO; 83 | typedef struct RedisModuleType RedisModuleType; 84 | typedef struct RedisModuleDigest RedisModuleDigest; 85 | 86 | typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc); 87 | 88 | typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); 89 | typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); 90 | typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); 91 | typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); 92 | typedef void (*RedisModuleTypeFreeFunc)(void *value); 93 | 94 | #define REDISMODULE_GET_API(name) \ 95 | RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name)) 96 | 97 | #define REDISMODULE_API_FUNC(x) (*x) 98 | 99 | 100 | void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); 101 | void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes); 102 | void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); 103 | void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size); 104 | char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str); 105 | int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *); 106 | int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); 107 | int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver); 108 | int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx); 109 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll); 110 | int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx); 111 | int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid); 112 | void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode); 113 | void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp); 114 | int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp); 115 | size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp); 116 | int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele); 117 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where); 118 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 119 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len); 120 | void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply); 121 | int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply); 122 | long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply); 123 | size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply); 124 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx); 125 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len); 126 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll); 127 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str); 128 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...); 129 | void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str); 130 | const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len); 131 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err); 132 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg); 133 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len); 134 | void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len); 135 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len); 136 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str); 137 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx); 138 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d); 139 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply); 140 | int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll); 141 | int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d); 142 | void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx); 143 | int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 144 | int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx); 145 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len); 146 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply); 147 | int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key); 148 | int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str); 149 | char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode); 150 | int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen); 151 | mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key); 152 | int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire); 153 | int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr); 154 | int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore); 155 | int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score); 156 | int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted); 157 | void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key); 158 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 159 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 160 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 161 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 162 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score); 163 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key); 164 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); 165 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); 166 | int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); 167 | int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...); 168 | int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx); 169 | void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos); 170 | unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx); 171 | void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes); 172 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeLoadFunc rdb_load, RedisModuleTypeSaveFunc rdb_save, RedisModuleTypeRewriteFunc aof_rewrite, RedisModuleTypeDigestFunc digest, RedisModuleTypeFreeFunc free); 173 | int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value); 174 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key); 175 | void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); 176 | void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); 177 | uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); 178 | void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); 179 | int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io); 180 | void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...); 181 | void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s); 182 | void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len); 183 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io); 184 | char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr); 185 | void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value); 186 | double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io); 187 | void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value); 188 | float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); 189 | void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); 190 | void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); 191 | int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); 192 | void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); 193 | int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); 194 | RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); 195 | 196 | /* This is included inline inside each Redis module. */ 197 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused)); 198 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { 199 | void *getapifuncptr = ((void**)ctx)[0]; 200 | RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; 201 | REDISMODULE_GET_API(Alloc); 202 | REDISMODULE_GET_API(Calloc); 203 | REDISMODULE_GET_API(Free); 204 | REDISMODULE_GET_API(Realloc); 205 | REDISMODULE_GET_API(Strdup); 206 | REDISMODULE_GET_API(CreateCommand); 207 | REDISMODULE_GET_API(SetModuleAttribs); 208 | REDISMODULE_GET_API(WrongArity); 209 | REDISMODULE_GET_API(ReplyWithLongLong); 210 | REDISMODULE_GET_API(ReplyWithError); 211 | REDISMODULE_GET_API(ReplyWithSimpleString); 212 | REDISMODULE_GET_API(ReplyWithArray); 213 | REDISMODULE_GET_API(ReplySetArrayLength); 214 | REDISMODULE_GET_API(ReplyWithStringBuffer); 215 | REDISMODULE_GET_API(ReplyWithString); 216 | REDISMODULE_GET_API(ReplyWithNull); 217 | REDISMODULE_GET_API(ReplyWithCallReply); 218 | REDISMODULE_GET_API(ReplyWithDouble); 219 | REDISMODULE_GET_API(ReplySetArrayLength); 220 | REDISMODULE_GET_API(GetSelectedDb); 221 | REDISMODULE_GET_API(SelectDb); 222 | REDISMODULE_GET_API(OpenKey); 223 | REDISMODULE_GET_API(CloseKey); 224 | REDISMODULE_GET_API(KeyType); 225 | REDISMODULE_GET_API(ValueLength); 226 | REDISMODULE_GET_API(ListPush); 227 | REDISMODULE_GET_API(ListPop); 228 | REDISMODULE_GET_API(StringToLongLong); 229 | REDISMODULE_GET_API(StringToDouble); 230 | REDISMODULE_GET_API(Call); 231 | REDISMODULE_GET_API(CallReplyProto); 232 | REDISMODULE_GET_API(FreeCallReply); 233 | REDISMODULE_GET_API(CallReplyInteger); 234 | REDISMODULE_GET_API(CallReplyType); 235 | REDISMODULE_GET_API(CallReplyLength); 236 | REDISMODULE_GET_API(CallReplyArrayElement); 237 | REDISMODULE_GET_API(CallReplyStringPtr); 238 | REDISMODULE_GET_API(CreateStringFromCallReply); 239 | REDISMODULE_GET_API(CreateString); 240 | REDISMODULE_GET_API(CreateStringFromLongLong); 241 | REDISMODULE_GET_API(CreateStringFromString); 242 | REDISMODULE_GET_API(CreateStringPrintf); 243 | REDISMODULE_GET_API(FreeString); 244 | REDISMODULE_GET_API(StringPtrLen); 245 | REDISMODULE_GET_API(AutoMemory); 246 | REDISMODULE_GET_API(Replicate); 247 | REDISMODULE_GET_API(ReplicateVerbatim); 248 | REDISMODULE_GET_API(DeleteKey); 249 | REDISMODULE_GET_API(StringSet); 250 | REDISMODULE_GET_API(StringDMA); 251 | REDISMODULE_GET_API(StringTruncate); 252 | REDISMODULE_GET_API(GetExpire); 253 | REDISMODULE_GET_API(SetExpire); 254 | REDISMODULE_GET_API(ZsetAdd); 255 | REDISMODULE_GET_API(ZsetIncrby); 256 | REDISMODULE_GET_API(ZsetScore); 257 | REDISMODULE_GET_API(ZsetRem); 258 | REDISMODULE_GET_API(ZsetRangeStop); 259 | REDISMODULE_GET_API(ZsetFirstInScoreRange); 260 | REDISMODULE_GET_API(ZsetLastInScoreRange); 261 | REDISMODULE_GET_API(ZsetFirstInLexRange); 262 | REDISMODULE_GET_API(ZsetLastInLexRange); 263 | REDISMODULE_GET_API(ZsetRangeCurrentElement); 264 | REDISMODULE_GET_API(ZsetRangeNext); 265 | REDISMODULE_GET_API(ZsetRangePrev); 266 | REDISMODULE_GET_API(ZsetRangeEndReached); 267 | REDISMODULE_GET_API(HashSet); 268 | REDISMODULE_GET_API(HashGet); 269 | REDISMODULE_GET_API(IsKeysPositionRequest); 270 | REDISMODULE_GET_API(KeyAtPos); 271 | REDISMODULE_GET_API(GetClientId); 272 | REDISMODULE_GET_API(PoolAlloc); 273 | REDISMODULE_GET_API(CreateDataType); 274 | REDISMODULE_GET_API(ModuleTypeSetValue); 275 | REDISMODULE_GET_API(ModuleTypeGetType); 276 | REDISMODULE_GET_API(ModuleTypeGetValue); 277 | REDISMODULE_GET_API(SaveUnsigned); 278 | REDISMODULE_GET_API(LoadUnsigned); 279 | REDISMODULE_GET_API(SaveSigned); 280 | REDISMODULE_GET_API(LoadSigned); 281 | REDISMODULE_GET_API(SaveString); 282 | REDISMODULE_GET_API(SaveStringBuffer); 283 | REDISMODULE_GET_API(LoadString); 284 | REDISMODULE_GET_API(LoadStringBuffer); 285 | REDISMODULE_GET_API(SaveDouble); 286 | REDISMODULE_GET_API(LoadDouble); 287 | REDISMODULE_GET_API(SaveFloat); 288 | REDISMODULE_GET_API(LoadFloat); 289 | REDISMODULE_GET_API(EmitAOF); 290 | REDISMODULE_GET_API(Log); 291 | REDISMODULE_GET_API(LogIOError); 292 | REDISMODULE_GET_API(StringAppendBuffer); 293 | REDISMODULE_GET_API(RetainString); 294 | REDISMODULE_GET_API(StringCompare); 295 | REDISMODULE_GET_API(GetContextFromIO); 296 | 297 | RedisModule_SetModuleAttribs(ctx,name,ver,apiver); 298 | return REDISMODULE_OK; 299 | } 300 | 301 | #else 302 | 303 | /* Things only defined for the modules core, not exported to modules 304 | * including this file. */ 305 | #define RedisModuleString robj 306 | 307 | #endif /* REDISMODULE_CORE */ 308 | #endif /* REDISMOUDLE_H */ 309 | -------------------------------------------------------------------------------- /rmutil/Makefile: -------------------------------------------------------------------------------- 1 | # set environment variable RM_INCLUDE_DIR to the location of redismodule.h 2 | ifndef RM_INCLUDE_DIR 3 | RM_INCLUDE_DIR=../ 4 | endif 5 | 6 | CFLAGS = -g -fPIC -lc -lm -O3 -std=gnu99 -I$(RM_INCLUDE_DIR) -Wall -Wno-unused-function 7 | CC=gcc 8 | 9 | OBJS=util.o strings.o sds.o vector.o heap.o priority_queue.o 10 | 11 | all: librmutil.a 12 | 13 | clean: 14 | rm -rf *.o *.a 15 | 16 | librmutil.a: $(OBJS) 17 | ar rcs $@ $^ 18 | 19 | test_vector: test_vector.o vector.o 20 | $(CC) -Wall -o test_vector vector.o test_vector.o -lc -O0 21 | @(sh -c ./test_vector) 22 | 23 | test_heap: test_heap.o heap.o vector.o 24 | $(CC) -Wall -o test_heap heap.o vector.o test_heap.o -lc -O0 25 | @(sh -c ./test_heap) 26 | 27 | test_priority_queue: test_priority_queue.o priority_queue.o heap.o vector.o 28 | $(CC) -Wall -o test_priority_queue priority_queue.o heap.o vector.o test_priority_queue.o -lc -O0 29 | @(sh -c ./test_heap) 30 | -------------------------------------------------------------------------------- /rmutil/alloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../redismodule.h" 6 | #include "alloc.h" 7 | 8 | /* A patched implementation of strdup that will use our patched calloc */ 9 | char *rmalloc_strndup(const char *s, size_t n) { 10 | char *ret = calloc(n + 1, sizeof(char)); 11 | if (ret) 12 | memcpy(ret, s, n); 13 | return ret; 14 | } 15 | 16 | /* 17 | * Re-patching RedisModule_Alloc and friends to the original malloc functions 18 | * 19 | * This function shold be called if you are working with malloc-patched code 20 | * ouside of redis, usually for unit tests. Call it once when entering your unit 21 | * tests' main(). 22 | * 23 | * Since including "alloc.h" while defining REDIS_MODULE_TARGET 24 | * replaces all malloc functions in redis with the RM_Alloc family of functions, 25 | * when running that code outside of redis, your app will crash. This function 26 | * patches the RM_Alloc functions back to the original mallocs. */ 27 | void RMUTil_InitAlloc() { 28 | 29 | RedisModule_Alloc = malloc; 30 | RedisModule_Realloc = realloc; 31 | RedisModule_Calloc = calloc; 32 | RedisModule_Free = free; 33 | RedisModule_Strdup = strdup; 34 | } 35 | -------------------------------------------------------------------------------- /rmutil/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_ALLOC__ 2 | #define __RMUTIL_ALLOC__ 3 | 4 | /* Automatic Redis Module Allocation functions monkey-patching. 5 | * 6 | * Including this file while REDIS_MODULE_TARGET is defined, will explicitly 7 | * override malloc, calloc, realloc & free with RedisModule_Alloc, 8 | * RedisModule_Callc, etc implementations, that allow Redis better control and 9 | * reporting over allocations per module. 10 | * 11 | * You should include this file in all c files AS THE LAST INCLUDED FILE 12 | * 13 | * This only has effect when when compiling with the macro REDIS_MODULE_TARGET 14 | * defined. The idea is that for unit tests it will not be defined, but for the 15 | * module build target it will be. 16 | * 17 | */ 18 | 19 | #include 20 | #include "../redismodule.h" 21 | 22 | char *rmalloc_strndup(const char *s, size_t n); 23 | 24 | #ifdef REDIS_MODULE_TARGET /* Set this when compiling your code as a module */ 25 | 26 | #define malloc(size) RedisModule_Alloc(size) 27 | #define calloc(count, size) RedisModule_Calloc(count, size) 28 | #define realloc(ptr, size) RedisModule_Realloc(ptr, size) 29 | #define free(ptr) RedisModule_Free(ptr) 30 | #define strdup(ptr) RedisModule_Strdup(ptr) 31 | 32 | // /* More overriding */ 33 | // // needed to avoid calling strndup->malloc 34 | #ifdef strndup 35 | #undef strndup 36 | #endif 37 | #define strndup(s, n) rmalloc_strndup(s, n) 38 | 39 | #else 40 | /* This function shold be called if you are working with malloc-patched code 41 | * ouside of redis, usually for unit tests. Call it once when entering your unit 42 | * tests' main() */ 43 | void RMUTil_InitAlloc(); 44 | #endif /* REDIS_MODULE_TARGET */ 45 | 46 | #endif /* __RMUTIL_ALLOC__ */ -------------------------------------------------------------------------------- /rmutil/heap.c: -------------------------------------------------------------------------------- 1 | #include "heap.h" 2 | 3 | /* Byte-wise swap two items of size SIZE. */ 4 | #define SWAP(a, b, size) \ 5 | do \ 6 | { \ 7 | register size_t __size = (size); \ 8 | register char *__a = (a), *__b = (b); \ 9 | do \ 10 | { \ 11 | char __tmp = *__a; \ 12 | *__a++ = *__b; \ 13 | *__b++ = __tmp; \ 14 | } while (--__size > 0); \ 15 | } while (0) 16 | 17 | inline char *__vector_GetPtr(Vector *v, size_t pos) { 18 | return v->data + (pos * v->elemSize); 19 | } 20 | 21 | void __sift_up(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 22 | size_t len = last - first; 23 | if (len > 1) { 24 | len = (len - 2) / 2; 25 | size_t ptr = first + len; 26 | if (cmp(__vector_GetPtr(v, ptr), __vector_GetPtr(v, --last)) < 0) { 27 | char t[v->elemSize]; 28 | memcpy(t, __vector_GetPtr(v, last), v->elemSize); 29 | do { 30 | memcpy(__vector_GetPtr(v, last), __vector_GetPtr(v, ptr), v->elemSize); 31 | last = ptr; 32 | if (len == 0) 33 | break; 34 | len = (len - 1) / 2; 35 | ptr = first + len; 36 | } while (cmp(__vector_GetPtr(v, ptr), t) < 0); 37 | memcpy(__vector_GetPtr(v, last), t, v->elemSize); 38 | } 39 | } 40 | } 41 | 42 | void __sift_down(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *), size_t start) { 43 | // left-child of __start is at 2 * __start + 1 44 | // right-child of __start is at 2 * __start + 2 45 | size_t len = last - first; 46 | size_t child = start - first; 47 | 48 | if (len < 2 || (len - 2) / 2 < child) 49 | return; 50 | 51 | child = 2 * child + 1; 52 | 53 | if ((child + 1) < len && cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, first + child + 1)) < 0) { 54 | // right-child exists and is greater than left-child 55 | ++child; 56 | } 57 | 58 | // check if we are in heap-order 59 | if (cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, start)) < 0) 60 | // we are, __start is larger than it's largest child 61 | return; 62 | 63 | char top[v->elemSize]; 64 | memcpy(top, __vector_GetPtr(v, start), v->elemSize); 65 | do { 66 | // we are not in heap-order, swap the parent with it's largest child 67 | memcpy(__vector_GetPtr(v, start), __vector_GetPtr(v, first + child), v->elemSize); 68 | start = first + child; 69 | 70 | if ((len - 2) / 2 < child) 71 | break; 72 | 73 | // recompute the child based off of the updated parent 74 | child = 2 * child + 1; 75 | 76 | if ((child + 1) < len && cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, first + child + 1)) < 0) { 77 | // right-child exists and is greater than left-child 78 | ++child; 79 | } 80 | 81 | // check if we are in heap-order 82 | } while (cmp(__vector_GetPtr(v, first + child), top) >= 0); 83 | memcpy(__vector_GetPtr(v, start), top, v->elemSize); 84 | } 85 | 86 | 87 | void Make_Heap(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 88 | if (last - first > 1) { 89 | // start from the first parent, there is no need to consider children 90 | for (int start = (last - first - 2) / 2; start >= 0; --start) { 91 | __sift_down(v, first, last, cmp, first + start); 92 | } 93 | } 94 | } 95 | 96 | 97 | inline void Heap_Push(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 98 | __sift_up(v, first, last, cmp); 99 | } 100 | 101 | 102 | inline void Heap_Pop(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 103 | if (last - first > 1) { 104 | SWAP(__vector_GetPtr(v, first), __vector_GetPtr(v, --last), v->elemSize); 105 | __sift_down(v, first, last, cmp, first); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rmutil/heap.h: -------------------------------------------------------------------------------- 1 | #ifndef __HEAP_H__ 2 | #define __HEAP_H__ 3 | 4 | #include "vector.h" 5 | 6 | 7 | /* Make heap from range 8 | * Rearranges the elements in the range [first,last) in such a way that they form a heap. 9 | * A heap is a way to organize the elements of a range that allows for fast retrieval of the element with the highest 10 | * value at any moment (with pop_heap), even repeatedly, while allowing for fast insertion of new elements (with 11 | * push_heap). 12 | * The element with the highest value is always pointed by first. The order of the other elements depends on the 13 | * particular implementation, but it is consistent throughout all heap-related functions of this header. 14 | * The elements are compared using cmp. 15 | */ 16 | void Make_Heap(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 17 | 18 | 19 | /* Push element into heap range 20 | * Given a heap in the range [first,last-1), this function extends the range considered a heap to [first,last) by 21 | * placing the value in (last-1) into its corresponding location within it. 22 | * A range can be organized into a heap by calling make_heap. After that, its heap properties are preserved if elements 23 | * are added and removed from it using push_heap and pop_heap, respectively. 24 | */ 25 | void Heap_Push(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 26 | 27 | 28 | /* Pop element from heap range 29 | * Rearranges the elements in the heap range [first,last) in such a way that the part considered a heap is shortened 30 | * by one: The element with the highest value is moved to (last-1). 31 | * While the element with the highest value is moved from first to (last-1) (which now is out of the heap), the other 32 | * elements are reorganized in such a way that the range [first,last-1) preserves the properties of a heap. 33 | * A range can be organized into a heap by calling make_heap. After that, its heap properties are preserved if elements 34 | * are added and removed from it using push_heap and pop_heap, respectively. 35 | */ 36 | void Heap_Pop(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 37 | 38 | #endif //__HEAP_H__ 39 | -------------------------------------------------------------------------------- /rmutil/logging.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_LOGGING_H__ 2 | #define __RMUTIL_LOGGING_H__ 3 | 4 | /* Convenience macros for redis logging */ 5 | 6 | #define RM_LOG_DEBUG(ctx, ...) RedisModule_Log(ctx, "debug", __VA_ARGS__) 7 | #define RM_LOG_VERBOSE(ctx, ...) RedisModule_Log(ctx, "verbose", __VA_ARGS__) 8 | #define RM_LOG_NOTICE(ctx, ...) RedisModule_Log(ctx, "notice", __VA_ARGS__) 9 | #define RM_LOG_WARNING(ctx, ...) RedisModule_Log(ctx, "warning", __VA_ARGS__) 10 | 11 | #endif -------------------------------------------------------------------------------- /rmutil/priority_queue.c: -------------------------------------------------------------------------------- 1 | #include "priority_queue.h" 2 | #include "heap.h" 3 | 4 | PriorityQueue *__newPriorityQueueSize(size_t elemSize, size_t cap, int (*cmp)(void *, void *)) { 5 | PriorityQueue *pq = malloc(sizeof(PriorityQueue)); 6 | pq->v = __newVectorSize(elemSize, cap); 7 | pq->cmp = cmp; 8 | return pq; 9 | } 10 | 11 | inline size_t Priority_Queue_Size(PriorityQueue *pq) { 12 | return Vector_Size(pq->v); 13 | } 14 | 15 | inline int Priority_Queue_Top(PriorityQueue *pq, void *ptr) { 16 | return Vector_Get(pq->v, 0, ptr); 17 | } 18 | 19 | inline size_t __priority_Queue_PushPtr(PriorityQueue *pq, void *elem) { 20 | size_t top = __vector_PushPtr(pq->v, elem); 21 | Heap_Push(pq->v, 0, top, pq->cmp); 22 | return top; 23 | } 24 | 25 | inline void Priority_Queue_Pop(PriorityQueue *pq) { 26 | if (pq->v->top == 0) { 27 | return; 28 | } 29 | Heap_Pop(pq->v, 0, pq->v->top, pq->cmp); 30 | pq->v->top--; 31 | } 32 | 33 | void Priority_Queue_Free(PriorityQueue *pq) { 34 | Vector_Free(pq->v); 35 | free(pq); 36 | } 37 | -------------------------------------------------------------------------------- /rmutil/priority_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef __PRIORITY_QUEUE_H__ 2 | #define __PRIORITY_QUEUE_H__ 3 | 4 | #include "vector.h" 5 | 6 | /* Priority queue 7 | * Priority queues are designed such that its first element is always the greatest of the elements it contains. 8 | * This context is similar to a heap, where elements can be inserted at any moment, and only the max heap element can be 9 | * retrieved (the one at the top in the priority queue). 10 | * Priority queues are implemented as Vectors. Elements are popped from the "back" of Vector, which is known as the top 11 | * of the priority queue. 12 | */ 13 | typedef struct { 14 | Vector *v; 15 | 16 | int (*cmp)(void *, void *); 17 | } PriorityQueue; 18 | 19 | /* Construct priority queue 20 | * Constructs a priority_queue container adaptor object. 21 | */ 22 | PriorityQueue *__newPriorityQueueSize(size_t elemSize, size_t cap, int (*cmp)(void *, void *)); 23 | 24 | #define NewPriorityQueue(type, cap, cmp) __newPriorityQueueSize(sizeof(type), cap, cmp) 25 | 26 | /* Return size 27 | * Returns the number of elements in the priority_queue. 28 | */ 29 | size_t Priority_Queue_Size(PriorityQueue *pq); 30 | 31 | /* Access top element 32 | * Copy the top element in the priority_queue to ptr. 33 | * The top element is the element that compares higher in the priority_queue. 34 | */ 35 | int Priority_Queue_Top(PriorityQueue *pq, void *ptr); 36 | 37 | /* Insert element 38 | * Inserts a new element in the priority_queue. 39 | */ 40 | size_t __priority_Queue_PushPtr(PriorityQueue *pq, void *elem); 41 | 42 | #define Priority_Queue_Push(pq, elem) __priority_Queue_PushPtr(pq, &(typeof(elem)){elem}) 43 | 44 | /* Remove top element 45 | * Removes the element on top of the priority_queue, effectively reducing its size by one. The element removed is the 46 | * one with the highest value. 47 | * The value of this element can be retrieved before being popped by calling Priority_Queue_Top. 48 | */ 49 | void Priority_Queue_Pop(PriorityQueue *pq); 50 | 51 | /* free the priority queue and the underlying data. Does not release its elements if 52 | * they are pointers */ 53 | void Priority_Queue_Free(PriorityQueue *pq); 54 | 55 | #endif //__PRIORITY_QUEUE_H__ 56 | -------------------------------------------------------------------------------- /rmutil/sds.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __SDS_H 34 | #define __SDS_H 35 | 36 | #define SDS_MAX_PREALLOC (1024*1024) 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | typedef char *sds; 43 | 44 | /* Note: sdshdr5 is never used, we just access the flags byte directly. 45 | * However is here to document the layout of type 5 SDS strings. */ 46 | struct __attribute__ ((__packed__)) sdshdr5 { 47 | unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ 48 | char buf[]; 49 | }; 50 | struct __attribute__ ((__packed__)) sdshdr8 { 51 | uint8_t len; /* used */ 52 | uint8_t alloc; /* excluding the header and null terminator */ 53 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 54 | char buf[]; 55 | }; 56 | struct __attribute__ ((__packed__)) sdshdr16 { 57 | uint16_t len; /* used */ 58 | uint16_t alloc; /* excluding the header and null terminator */ 59 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 60 | char buf[]; 61 | }; 62 | struct __attribute__ ((__packed__)) sdshdr32 { 63 | uint32_t len; /* used */ 64 | uint32_t alloc; /* excluding the header and null terminator */ 65 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 66 | char buf[]; 67 | }; 68 | struct __attribute__ ((__packed__)) sdshdr64 { 69 | uint64_t len; /* used */ 70 | uint64_t alloc; /* excluding the header and null terminator */ 71 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 72 | char buf[]; 73 | }; 74 | 75 | #define SDS_TYPE_5 0 76 | #define SDS_TYPE_8 1 77 | #define SDS_TYPE_16 2 78 | #define SDS_TYPE_32 3 79 | #define SDS_TYPE_64 4 80 | #define SDS_TYPE_MASK 7 81 | #define SDS_TYPE_BITS 3 82 | #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); 83 | #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) 84 | #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) 85 | 86 | static inline size_t sdslen(const sds s) { 87 | unsigned char flags = s[-1]; 88 | switch(flags&SDS_TYPE_MASK) { 89 | case SDS_TYPE_5: 90 | return SDS_TYPE_5_LEN(flags); 91 | case SDS_TYPE_8: 92 | return SDS_HDR(8,s)->len; 93 | case SDS_TYPE_16: 94 | return SDS_HDR(16,s)->len; 95 | case SDS_TYPE_32: 96 | return SDS_HDR(32,s)->len; 97 | case SDS_TYPE_64: 98 | return SDS_HDR(64,s)->len; 99 | } 100 | return 0; 101 | } 102 | 103 | static inline size_t sdsavail(const sds s) { 104 | unsigned char flags = s[-1]; 105 | switch(flags&SDS_TYPE_MASK) { 106 | case SDS_TYPE_5: { 107 | return 0; 108 | } 109 | case SDS_TYPE_8: { 110 | SDS_HDR_VAR(8,s); 111 | return sh->alloc - sh->len; 112 | } 113 | case SDS_TYPE_16: { 114 | SDS_HDR_VAR(16,s); 115 | return sh->alloc - sh->len; 116 | } 117 | case SDS_TYPE_32: { 118 | SDS_HDR_VAR(32,s); 119 | return sh->alloc - sh->len; 120 | } 121 | case SDS_TYPE_64: { 122 | SDS_HDR_VAR(64,s); 123 | return sh->alloc - sh->len; 124 | } 125 | } 126 | return 0; 127 | } 128 | 129 | static inline void sdssetlen(sds s, size_t newlen) { 130 | unsigned char flags = s[-1]; 131 | switch(flags&SDS_TYPE_MASK) { 132 | case SDS_TYPE_5: 133 | { 134 | unsigned char *fp = ((unsigned char*)s)-1; 135 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 136 | } 137 | break; 138 | case SDS_TYPE_8: 139 | SDS_HDR(8,s)->len = newlen; 140 | break; 141 | case SDS_TYPE_16: 142 | SDS_HDR(16,s)->len = newlen; 143 | break; 144 | case SDS_TYPE_32: 145 | SDS_HDR(32,s)->len = newlen; 146 | break; 147 | case SDS_TYPE_64: 148 | SDS_HDR(64,s)->len = newlen; 149 | break; 150 | } 151 | } 152 | 153 | static inline void sdsinclen(sds s, size_t inc) { 154 | unsigned char flags = s[-1]; 155 | switch(flags&SDS_TYPE_MASK) { 156 | case SDS_TYPE_5: 157 | { 158 | unsigned char *fp = ((unsigned char*)s)-1; 159 | unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; 160 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 161 | } 162 | break; 163 | case SDS_TYPE_8: 164 | SDS_HDR(8,s)->len += inc; 165 | break; 166 | case SDS_TYPE_16: 167 | SDS_HDR(16,s)->len += inc; 168 | break; 169 | case SDS_TYPE_32: 170 | SDS_HDR(32,s)->len += inc; 171 | break; 172 | case SDS_TYPE_64: 173 | SDS_HDR(64,s)->len += inc; 174 | break; 175 | } 176 | } 177 | 178 | /* sdsalloc() = sdsavail() + sdslen() */ 179 | static inline size_t sdsalloc(const sds s) { 180 | unsigned char flags = s[-1]; 181 | switch(flags&SDS_TYPE_MASK) { 182 | case SDS_TYPE_5: 183 | return SDS_TYPE_5_LEN(flags); 184 | case SDS_TYPE_8: 185 | return SDS_HDR(8,s)->alloc; 186 | case SDS_TYPE_16: 187 | return SDS_HDR(16,s)->alloc; 188 | case SDS_TYPE_32: 189 | return SDS_HDR(32,s)->alloc; 190 | case SDS_TYPE_64: 191 | return SDS_HDR(64,s)->alloc; 192 | } 193 | return 0; 194 | } 195 | 196 | static inline void sdssetalloc(sds s, size_t newlen) { 197 | unsigned char flags = s[-1]; 198 | switch(flags&SDS_TYPE_MASK) { 199 | case SDS_TYPE_5: 200 | /* Nothing to do, this type has no total allocation info. */ 201 | break; 202 | case SDS_TYPE_8: 203 | SDS_HDR(8,s)->alloc = newlen; 204 | break; 205 | case SDS_TYPE_16: 206 | SDS_HDR(16,s)->alloc = newlen; 207 | break; 208 | case SDS_TYPE_32: 209 | SDS_HDR(32,s)->alloc = newlen; 210 | break; 211 | case SDS_TYPE_64: 212 | SDS_HDR(64,s)->alloc = newlen; 213 | break; 214 | } 215 | } 216 | 217 | sds sdsnewlen(const void *init, size_t initlen); 218 | sds sdsnew(const char *init); 219 | sds sdsempty(void); 220 | sds sdsdup(const sds s); 221 | void sdsfree(sds s); 222 | sds sdsgrowzero(sds s, size_t len); 223 | sds sdscatlen(sds s, const void *t, size_t len); 224 | sds sdscat(sds s, const char *t); 225 | sds sdscatsds(sds s, const sds t); 226 | sds sdscpylen(sds s, const char *t, size_t len); 227 | sds sdscpy(sds s, const char *t); 228 | 229 | sds sdscatvprintf(sds s, const char *fmt, va_list ap); 230 | #ifdef __GNUC__ 231 | sds sdscatprintf(sds s, const char *fmt, ...) 232 | __attribute__((format(printf, 2, 3))); 233 | #else 234 | sds sdscatprintf(sds s, const char *fmt, ...); 235 | #endif 236 | 237 | sds sdscatfmt(sds s, char const *fmt, ...); 238 | sds sdstrim(sds s, const char *cset); 239 | void sdsrange(sds s, int start, int end); 240 | void sdsupdatelen(sds s); 241 | void sdsclear(sds s); 242 | int sdscmp(const sds s1, const sds s2); 243 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); 244 | void sdsfreesplitres(sds *tokens, int count); 245 | void sdstolower(sds s); 246 | void sdstoupper(sds s); 247 | sds sdsfromlonglong(long long value); 248 | sds sdscatrepr(sds s, const char *p, size_t len); 249 | sds *sdssplitargs(const char *line, int *argc); 250 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); 251 | sds sdsjoin(char **argv, int argc, char *sep); 252 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); 253 | 254 | /* Low level functions exposed to the user API */ 255 | sds sdsMakeRoomFor(sds s, size_t addlen); 256 | void sdsIncrLen(sds s, int incr); 257 | sds sdsRemoveFreeSpace(sds s); 258 | size_t sdsAllocSize(sds s); 259 | void *sdsAllocPtr(sds s); 260 | 261 | /* Export the allocator used by SDS to the program using SDS. 262 | * Sometimes the program SDS is linked to, may use a different set of 263 | * allocators, but may want to allocate or free things that SDS will 264 | * respectively free or allocate. */ 265 | void *sds_malloc(size_t size); 266 | void *sds_realloc(void *ptr, size_t size); 267 | void sds_free(void *ptr); 268 | 269 | #ifdef REDIS_TEST 270 | int sdsTest(int argc, char *argv[]); 271 | #endif 272 | 273 | #endif 274 | -------------------------------------------------------------------------------- /rmutil/sdsalloc.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Redis Labs, Inc 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Redis nor the names of its contributors may be used 16 | * to endorse or promote products derived from this software without 17 | * specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | /* SDS allocator selection. 33 | * 34 | * This file is used in order to change the SDS allocator at compile time. 35 | * Just define the following defines to what you want to use. Also add 36 | * the include of your alternate allocator if needed (not needed in order 37 | * to use the default libc allocator). */ 38 | 39 | //#include "zmalloc.h" 40 | #define s_malloc malloc 41 | #define s_realloc realloc 42 | #define s_free free 43 | -------------------------------------------------------------------------------- /rmutil/strings.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "strings.h" 5 | 6 | 7 | #include "sds.h" 8 | 9 | RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...) { 10 | sds s = sdsempty(); 11 | 12 | va_list ap; 13 | va_start(ap, fmt); 14 | s = sdscatvprintf(s, fmt, ap); 15 | va_end(ap); 16 | 17 | RedisModuleString *ret = RedisModule_CreateString(ctx, (const char *)s, sdslen(s)); 18 | sdsfree(s); 19 | return ret; 20 | } 21 | 22 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2) { 23 | 24 | 25 | const char *c1, *c2; 26 | size_t l1, l2; 27 | c1 = RedisModule_StringPtrLen(s1, &l1); 28 | c2 = RedisModule_StringPtrLen(s2, &l2); 29 | if (l1 != l2) return 0; 30 | 31 | return strncmp(c1, c2, l1) == 0; 32 | } 33 | 34 | int RMUtil_StringEqualsC(RedisModuleString *s1, const char *s2) { 35 | 36 | 37 | const char *c1; 38 | size_t l1, l2 = strlen(s2); 39 | c1 = RedisModule_StringPtrLen(s1, &l1); 40 | if (l1 != l2) return 0; 41 | 42 | return strncmp(c1, s2, l1) == 0; 43 | } 44 | 45 | void RMUtil_StringToLower(RedisModuleString *s) { 46 | 47 | size_t l; 48 | char *c = (char *)RedisModule_StringPtrLen(s, &l); 49 | size_t i; 50 | for (i = 0; i < l; i++) { 51 | *c = tolower(*c); 52 | ++c; 53 | } 54 | 55 | } 56 | 57 | void RMUtil_StringToUpper(RedisModuleString *s) { 58 | size_t l; 59 | char *c = (char *)RedisModule_StringPtrLen(s, &l); 60 | size_t i; 61 | for (i = 0; i < l; i++) { 62 | *c = toupper(*c); 63 | ++c; 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /rmutil/strings.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_STRINGS_H__ 2 | #define __RMUTIL_STRINGS_H__ 3 | 4 | #include 5 | 6 | /* 7 | * Create a new RedisModuleString object from a printf-style format and arguments. 8 | * Note that RedisModuleString objects CANNOT be used as formatting arguments. 9 | */ 10 | RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...); 11 | 12 | /* Return 1 if the two strings are equal. Case *sensitive* */ 13 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2); 14 | 15 | /* Return 1 if the string is equal to a C NULL terminated string. Case *sensitive* */ 16 | int RMUtil_StringEqualsC(RedisModuleString *s1, const char *s2); 17 | 18 | /* Converts a redis string to lowercase in place without reallocating anything */ 19 | void RMUtil_StringToLower(RedisModuleString *s); 20 | 21 | /* Converts a redis string to uppercase in place without reallocating anything */ 22 | void RMUtil_StringToUpper(RedisModuleString *s); 23 | #endif 24 | -------------------------------------------------------------------------------- /rmutil/test_heap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "heap.h" 3 | #include "assert.h" 4 | 5 | int cmp(void *a, void *b) { 6 | int *__a = (int *) a; 7 | int *__b = (int *) b; 8 | return *__a - *__b; 9 | } 10 | 11 | int main(int argc, char **argv) { 12 | int myints[] = {10, 20, 30, 5, 15}; 13 | Vector *v = NewVector(int, 5); 14 | for (int i = 0; i < 5; i++) { 15 | Vector_Push(v, myints[i]); 16 | } 17 | 18 | Make_Heap(v, 0, v->top, cmp); 19 | 20 | int n; 21 | Vector_Get(v, 0, &n); 22 | assert(30 == n); 23 | 24 | Heap_Pop(v, 0, v->top, cmp); 25 | v->top = 4; 26 | Vector_Get(v, 0, &n); 27 | assert(20 == n); 28 | 29 | Vector_Push(v, 99); 30 | Heap_Push(v, 0, v->top, cmp); 31 | Vector_Get(v, 0, &n); 32 | assert(99 == n); 33 | 34 | Vector_Free(v); 35 | printf("PASS!"); 36 | return 0; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /rmutil/test_priority_queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "assert.h" 3 | #include "priority_queue.h" 4 | 5 | int cmp(void* i1, void* i2) { 6 | int *__i1 = (int*) i1; 7 | int *__i2 = (int*) i2; 8 | return *__i1 - *__i2; 9 | } 10 | 11 | int main(int argc, char **argv) { 12 | PriorityQueue *pq = NewPriorityQueue(int, 10, cmp); 13 | assert(0 == Priority_Queue_Size(pq)); 14 | 15 | for (int i = 0; i < 5; i++) { 16 | Priority_Queue_Push(pq, i); 17 | } 18 | assert(5 == Priority_Queue_Size(pq)); 19 | 20 | Priority_Queue_Pop(pq); 21 | assert(4 == Priority_Queue_Size(pq)); 22 | 23 | Priority_Queue_Push(pq, 10); 24 | Priority_Queue_Push(pq, 20); 25 | Priority_Queue_Push(pq, 15); 26 | int n; 27 | Priority_Queue_Top(pq, &n); 28 | assert(20 == n); 29 | 30 | Priority_Queue_Pop(pq); 31 | Priority_Queue_Top(pq, &n); 32 | assert(15 == n); 33 | 34 | Priority_Queue_Free(pq); 35 | printf("PASS!"); 36 | return 0; 37 | } -------------------------------------------------------------------------------- /rmutil/test_util.h: -------------------------------------------------------------------------------- 1 | #ifndef __TEST_UTIL_H__ 2 | #define __TEST_UTIL_H__ 3 | 4 | #include "util.h" 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | #define RMUtil_Test(f) \ 11 | if (argc < 2 || RMUtil_ArgExists(__STRING(f), argv, argc, 1)) { \ 12 | int rc = f(ctx); \ 13 | if (rc != REDISMODULE_OK) { \ 14 | RedisModule_ReplyWithError(ctx, "Test " __STRING(f) " FAILED"); \ 15 | return REDISMODULE_ERR;\ 16 | }\ 17 | } 18 | 19 | 20 | #define RMUtil_Assert(expr) if (!(expr)) { fprintf (stderr, "Assertion '%s' Failed\n", __STRING(expr)); return REDISMODULE_ERR; } 21 | 22 | #define RMUtil_AssertReplyEquals(rep, cstr) RMUtil_Assert( \ 23 | RMUtil_StringEquals(RedisModule_CreateStringFromCallReply(rep), RedisModule_CreateString(ctx, cstr, strlen(cstr))) \ 24 | ) 25 | 26 | #endif -------------------------------------------------------------------------------- /rmutil/test_vector.c: -------------------------------------------------------------------------------- 1 | #include "vector.h" 2 | #include 3 | #include "assert.h" 4 | 5 | int main(int argc, char **argv) { 6 | 7 | 8 | Vector *v = NewVector(int, 1); 9 | int N = 10; 10 | 11 | for (int i = 0; i < N/2; i++) { 12 | Vector_Put(v, i, i); 13 | } 14 | 15 | for (int i = N/2; i < N; i++) { 16 | Vector_Push(v, i); 17 | } 18 | assert(Vector_Size(v) == N); 19 | assert(Vector_Cap(v) >= N); 20 | 21 | for (int i = 0; i < Vector_Size(v); i++) { 22 | int n; 23 | int rc = Vector_Get(v, i, &n); 24 | printf("%d %d\n", rc, n); 25 | assert ( 1== rc ); 26 | assert (n == i); 27 | } 28 | 29 | Vector_Free(v); 30 | 31 | v = NewVector(char *, 0); 32 | N = 4; 33 | char *strings[4] = {"hello", "world", "foo", "bar"}; 34 | 35 | for (int i = 0; i < N/2; i++) { 36 | Vector_Put(v, i, strings[i]); 37 | } 38 | 39 | for (int i = N/2; i < N; i++) { 40 | Vector_Push(v, strings[i]); 41 | } 42 | assert(Vector_Size(v) == N); 43 | assert(Vector_Cap(v) >= N); 44 | 45 | for (size_t i = 0; i < Vector_Size(v); i++) { 46 | char *x; 47 | int rc = Vector_Get(v, i, &x); 48 | assert (rc == 1); 49 | assert (!strcmp(x, strings[i])); 50 | } 51 | 52 | int rc = Vector_Get(v, 100, NULL); 53 | assert (rc == 0); 54 | 55 | Vector_Free(v); 56 | printf("PASS!"); 57 | 58 | return 0; 59 | //Vector_Push(v, "hello"); 60 | //Vector_Push(v, "world"); 61 | // char *x = NULL; 62 | // int rc = Vector_Getx(v, 0, &x); 63 | // printf("rc: %d got %s\n", rc, x); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /rmutil/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "util.h" 11 | 12 | /** 13 | Check if an argument exists in an argument list (argv,argc), starting at offset. 14 | @return 0 if it doesn't exist, otherwise the offset it exists in 15 | */ 16 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset) { 17 | 18 | for (; offset < argc; offset++) { 19 | size_t l; 20 | const char *carg = RedisModule_StringPtrLen(argv[offset], &l); 21 | if (carg != NULL && strcasecmp(carg, arg) == 0) { 22 | return offset; 23 | } 24 | } 25 | return 0; 26 | } 27 | 28 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx) { 29 | 30 | RedisModuleCallReply *r = RedisModule_Call(ctx, "INFO", "c", "all"); 31 | if (r == NULL || RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { 32 | return NULL; 33 | } 34 | 35 | 36 | int cap = 100; // rough estimate of info lines 37 | RMUtilInfo *info = malloc(sizeof(RMUtilInfo)); 38 | info->entries = calloc(cap, sizeof(RMUtilInfoEntry)); 39 | 40 | 41 | int i = 0; 42 | char *text = (char *)RedisModule_StringPtrLen(RedisModule_CreateStringFromCallReply(r), NULL); 43 | char *line = text; 44 | while (line) { 45 | char *line = strsep(&text, "\r\n"); 46 | if (line == NULL) break; 47 | 48 | if (!(*line >= 'a' && *line <= 'z')) { //skip non entry lines 49 | continue; 50 | } 51 | 52 | char *key = strsep(&line, ":"); 53 | info->entries[i].key = key; 54 | info->entries[i].val = line; 55 | printf("Got info '%s' = '%s'\n", key, line); 56 | i++; 57 | if (i >= cap) { 58 | cap *=2; 59 | info->entries = realloc(info->entries, cap*sizeof(RMUtilInfoEntry)); 60 | } 61 | } 62 | info->numEntries = i; 63 | 64 | return info; 65 | 66 | } 67 | void RMUtilRedisInfo_Free(RMUtilInfo *info) { 68 | 69 | free(info->entries); 70 | free(info); 71 | 72 | } 73 | 74 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val) { 75 | 76 | const char *p = NULL; 77 | if (!RMUtilInfo_GetString(info, key, &p)) { 78 | return 0; 79 | } 80 | 81 | *val = strtoll(p, NULL, 10); 82 | if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN)) || 83 | (errno != 0 && *val == 0)) { 84 | *val = -1; 85 | return 0; 86 | } 87 | 88 | 89 | return 1; 90 | } 91 | 92 | 93 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str) { 94 | int i; 95 | for (i = 0; i < info->numEntries; i++) { 96 | if (!strcmp(key, info->entries[i].key)) { 97 | *str = info->entries[i].val; 98 | return 1; 99 | } 100 | } 101 | return 0; 102 | } 103 | 104 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d) { 105 | const char *p = NULL; 106 | if (!RMUtilInfo_GetString(info, key, &p)) { 107 | printf("not found %s\n", key); 108 | return 0; 109 | } 110 | 111 | *d = strtod(p, NULL); 112 | printf("p: %s, d: %f\n",p,*d); 113 | if ((errno == ERANGE && (*d == HUGE_VAL || *d == -HUGE_VAL)) || 114 | (errno != 0 && *d == 0)) { 115 | return 0; 116 | } 117 | 118 | 119 | return 1; 120 | } 121 | 122 | 123 | /* 124 | c -- pointer to a Null terminated C string pointer. 125 | s -- pointer to a RedisModuleString 126 | l -- pointer to Long long integer. 127 | d -- pointer to a Double 128 | * -- do not parse this argument at all 129 | */ 130 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...) { 131 | va_list ap; 132 | va_start(ap, fmt); 133 | int rc = rmutil_vparseArgs(argv, argc, offset, fmt, ap); 134 | va_end(ap); 135 | return rc; 136 | } 137 | 138 | 139 | // Internal function that parses arguments based on the format described above 140 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap) { 141 | 142 | int i = offset; 143 | char *c = (char *)fmt; 144 | while (*c && i < argc) { 145 | 146 | // read c string 147 | if (*c == 'c') { 148 | char **p = va_arg(ap, char**); 149 | *p = (char *)RedisModule_StringPtrLen(argv[i], NULL); 150 | } else if (*c == 's') { //read redis string 151 | 152 | RedisModuleString **s = va_arg(ap, void*); 153 | *s = argv[i]; 154 | 155 | } else if (*c == 'l') { //read long 156 | long long *l = va_arg(ap, long long *); 157 | 158 | if (RedisModule_StringToLongLong(argv[i], l) != REDISMODULE_OK) { 159 | return REDISMODULE_ERR; 160 | } 161 | } else if (*c == 'd') { //read double 162 | double *d = va_arg(ap, double *); 163 | if (RedisModule_StringToDouble(argv[i], d) != REDISMODULE_OK) { 164 | return REDISMODULE_ERR; 165 | } 166 | } else if (*c == '*') { //skip current arg 167 | //do nothing 168 | } else { 169 | return REDISMODULE_ERR; //WAT? 170 | } 171 | c++; 172 | i++; 173 | } 174 | // if the format is longer than argc, retun an error 175 | if (*c != 0) { 176 | return REDISMODULE_ERR; 177 | } 178 | return REDISMODULE_OK; 179 | 180 | 181 | } 182 | 183 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, ...) { 184 | 185 | int pos = RMUtil_ArgExists(token, argv, argc, 0); 186 | if (pos == 0) { 187 | return REDISMODULE_ERR; 188 | } 189 | 190 | va_list ap; 191 | va_start(ap, fmt); 192 | int rc = rmutil_vparseArgs(argv, argc, pos+1, fmt, ap); 193 | va_end(ap); 194 | return rc; 195 | 196 | } 197 | 198 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath( 199 | RedisModuleCallReply *rep, const char *path) { 200 | if (rep == NULL) return NULL; 201 | 202 | RedisModuleCallReply *ele = rep; 203 | const char *s = path; 204 | char *e; 205 | long idx; 206 | do { 207 | errno = 0; 208 | idx = strtol(s, &e, 10); 209 | 210 | if ((errno == ERANGE && (idx == LONG_MAX || idx == LONG_MIN)) || 211 | (errno != 0 && idx == 0) || 212 | (REDISMODULE_REPLY_ARRAY != RedisModule_CallReplyType(ele)) || 213 | (s == e)) { 214 | ele = NULL; 215 | break; 216 | } 217 | s = e; 218 | ele = RedisModule_CallReplyArrayElement(ele, idx - 1); 219 | 220 | } while ((ele != NULL) && (*e != '\0')); 221 | 222 | return ele; 223 | } -------------------------------------------------------------------------------- /rmutil/util.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTIL_H__ 2 | #define __UTIL_H__ 3 | 4 | #include 5 | #include 6 | 7 | /// make sure the response is not NULL or an error, and if it is sends the error to the client and exit the current function 8 | #define RMUTIL_ASSERT_NOERROR(r, cleanup) \ 9 | if (r == NULL) { \ 10 | cleanup(); \ 11 | return RedisModule_ReplyWithError(ctx,"ERR reply is NULL"); \ 12 | } else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { \ 13 | RedisModule_ReplyWithCallReply(ctx,r); \ 14 | cleanup(); \ 15 | return REDISMODULE_ERR; \ 16 | } 17 | 18 | #define __rmutil_register_cmd(ctx, cmd, f, mode) \ 19 | if (RedisModule_CreateCommand(ctx, cmd, f, \ 20 | (!strcmp(mode, "readonly ")) ? "readonly" : \ 21 | (!strcmp(mode, "write ")) ? "write" : mode, \ 22 | 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; 23 | 24 | #define RMUtil_RegisterReadCmd(ctx, cmd, f, ...) __rmutil_register_cmd(ctx, cmd, f, "readonly " __VA_ARGS__) 25 | 26 | #define RMUtil_RegisterWriteCmd(ctx, cmd, f, ...) __rmutil_register_cmd(ctx, cmd, f, "write " __VA_ARGS__) 27 | 28 | /* RedisModule utilities. */ 29 | 30 | /** Return the offset of an arg if it exists in the arg list, or 0 if it's not there */ 31 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset); 32 | 33 | /** 34 | Automatically conver the arg list to corresponding variable pointers according to a given format. 35 | You pass it the command arg list and count, the starting offset, a parsing format, and pointers to the variables. 36 | The format is a string consisting of the following identifiers: 37 | 38 | c -- pointer to a Null terminated C string pointer. 39 | s -- pointer to a RedisModuleString 40 | l -- pointer to Long long integer. 41 | d -- pointer to a Double 42 | * -- do not parse this argument at all 43 | 44 | Example: If I want to parse args[1], args[2] as a long long and double, I do: 45 | double d; 46 | long long l; 47 | RMUtil_ParseArgs(argv, argc, 1, "ld", &l, &d); 48 | */ 49 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...); 50 | 51 | /** 52 | Same as RMUtil_ParseArgs, but only parses the arguments after `token`, if it was found. 53 | This is useful for optional stuff like [LIMIT [offset] [limit]] 54 | */ 55 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, ...); 56 | 57 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap); 58 | 59 | // A single key/value entry in a redis info map 60 | typedef struct { 61 | const char *key; 62 | const char *val; 63 | } RMUtilInfoEntry; 64 | 65 | // Representation of INFO command response, as a list of k/v pairs 66 | typedef struct { 67 | RMUtilInfoEntry *entries; 68 | int numEntries; 69 | } RMUtilInfo; 70 | 71 | /** 72 | * Get redis INFO result and parse it as RMUtilInfo. 73 | * Returns NULL if something goes wrong. 74 | * The resulting object needs to be freed with RMUtilRedisInfo_Free 75 | */ 76 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx); 77 | 78 | /** 79 | * Free an RMUtilInfo object and its entries 80 | */ 81 | void RMUtilRedisInfo_Free(RMUtilInfo *info); 82 | 83 | /** 84 | * Get an integer value from an info object. Returns 1 if the value was found and 85 | * is an integer, 0 otherwise. the value is placed in 'val' 86 | */ 87 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val); 88 | 89 | /** 90 | * Get a string value from an info object. The value is placed in str. 91 | * Returns 1 if the key was found, 0 if not 92 | */ 93 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str); 94 | 95 | /** 96 | * Get a double value from an info object. Returns 1 if the value was found and is 97 | * a correctly formatted double, 0 otherwise. the value is placed in 'd' 98 | */ 99 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d); 100 | 101 | /* 102 | * Returns a call reply array's element given by a space-delimited path. E.g., 103 | * the path "1 2 3" will return the 3rd element from the 2 element of the 1st 104 | * element from an array (or NULL if not found) 105 | */ 106 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath( 107 | RedisModuleCallReply *rep, const char *path); 108 | 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /rmutil/vector.c: -------------------------------------------------------------------------------- 1 | #include "vector.h" 2 | #include 3 | 4 | inline int __vector_PushPtr(Vector *v, void *elem) { 5 | if (v->top == v->cap) { 6 | Vector_Resize(v, v->cap ? v->cap * 2 : 1); 7 | } 8 | 9 | __vector_PutPtr(v, v->top, elem); 10 | return v->top; 11 | } 12 | 13 | inline int Vector_Get(Vector *v, size_t pos, void *ptr) { 14 | // return 0 if pos is out of bounds 15 | if (pos >= v->top) { 16 | return 0; 17 | } 18 | 19 | memcpy(ptr, v->data + (pos * v->elemSize), v->elemSize); 20 | return 1; 21 | } 22 | 23 | /* Get the element at the end of the vector, decreasing the size by one */ 24 | inline int Vector_Pop(Vector *v, void *ptr) { 25 | if (v->top > 0) { 26 | if (ptr != NULL) { 27 | Vector_Get(v, v->top - 1, ptr); 28 | } 29 | v->top--; 30 | return 1; 31 | } 32 | return 0; 33 | } 34 | 35 | inline int __vector_PutPtr(Vector *v, size_t pos, void *elem) { 36 | // resize if pos is out of bounds 37 | if (pos >= v->cap) { 38 | Vector_Resize(v, pos + 1); 39 | } 40 | 41 | if (elem) { 42 | memcpy(v->data + pos * v->elemSize, elem, v->elemSize); 43 | } else { 44 | memset(v->data + pos * v->elemSize, 0, v->elemSize); 45 | } 46 | // move the end offset to pos + 1 if we grew 47 | if (pos >= v->top) { 48 | v->top = pos + 1; 49 | } 50 | return 1; 51 | } 52 | 53 | int Vector_Resize(Vector *v, size_t newcap) { 54 | int oldcap = v->cap; 55 | v->cap = newcap; 56 | 57 | v->data = realloc(v->data, v->cap * v->elemSize); 58 | 59 | // If we grew: 60 | // put all zeros at the newly realloc'd part of the vector 61 | if (newcap > oldcap) { 62 | int offset = oldcap * v->elemSize; 63 | memset(v->data + offset, 0, v->cap * v->elemSize - offset); 64 | } 65 | return v->cap; 66 | } 67 | 68 | Vector *__newVectorSize(size_t elemSize, size_t cap) { 69 | Vector *vec = malloc(sizeof(Vector)); 70 | vec->data = calloc(cap, elemSize); 71 | vec->top = 0; 72 | vec->elemSize = elemSize; 73 | vec->cap = cap; 74 | 75 | return vec; 76 | } 77 | 78 | void Vector_Free(Vector *v) { 79 | free(v->data); 80 | free(v); 81 | } 82 | 83 | inline int Vector_Size(Vector *v) { return v->top; } 84 | 85 | /* return the actual capacity */ 86 | inline int Vector_Cap(Vector *v) { return v->cap; } 87 | -------------------------------------------------------------------------------- /rmutil/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef __VECTOR_H__ 2 | #define __VECTOR_H__ 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * Generic resizable vector that can be used if you just want to store stuff 9 | * temporarily. 10 | * Works like C++ std::vector with an underlying resizable buffer 11 | */ 12 | typedef struct { 13 | char *data; 14 | size_t elemSize; 15 | size_t cap; 16 | size_t top; 17 | 18 | } Vector; 19 | 20 | /* Create a new vector with element size. This should generally be used 21 | * internall by the NewVector macro */ 22 | Vector *__newVectorSize(size_t elemSize, size_t cap); 23 | 24 | // Put a pointer in the vector. To be used internall by the library 25 | int __vector_PutPtr(Vector *v, size_t pos, void *elem); 26 | 27 | /* 28 | * Create a new vector for a given type and a given capacity. 29 | * e.g. NewVector(int, 0) - empty vector of ints 30 | */ 31 | #define NewVector(type, cap) __newVectorSize(sizeof(type), cap) 32 | 33 | /* 34 | * get the element at index pos. The value is copied in to ptr. If pos is outside 35 | * the vector capacity, we return 0 36 | * otherwise 1 37 | */ 38 | int Vector_Get(Vector *v, size_t pos, void *ptr); 39 | 40 | /* Get the element at the end of the vector, decreasing the size by one */ 41 | int Vector_Pop(Vector *v, void *ptr); 42 | 43 | //#define Vector_Getx(v, pos, ptr) pos < v->cap ? 1 : 0; *ptr = 44 | //*(typeof(ptr))(v->data + v->elemSize*pos) 45 | 46 | /* 47 | * Put an element at pos. 48 | * Note: If pos is outside the vector capacity, we resize it accordingly 49 | */ 50 | #define Vector_Put(v, pos, elem) \ 51 | __vector_PutPtr(v, pos, elem ? &(typeof(elem)){elem} : NULL) 52 | 53 | /* Push an element at the end of v, resizing it if needed. This macro wraps 54 | * __vector_PushPtr */ 55 | #define Vector_Push(v, elem) \ 56 | __vector_PushPtr(v, elem ? &(typeof(elem)){elem} : NULL) 57 | 58 | int __vector_PushPtr(Vector *v, void *elem); 59 | 60 | /* resize capacity of v */ 61 | int Vector_Resize(Vector *v, size_t newcap); 62 | 63 | /* return the used size of the vector, regardless of capacity */ 64 | int Vector_Size(Vector *v); 65 | 66 | /* return the actual capacity */ 67 | int Vector_Cap(Vector *v); 68 | 69 | /* free the vector and the underlying data. Does not release its elements if 70 | * they are pointers*/ 71 | void Vector_Free(Vector *v); 72 | 73 | int __vecotr_PutPtr(Vector *v, size_t pos, void *elem); 74 | 75 | #endif -------------------------------------------------------------------------------- /timeseries/Makefile: -------------------------------------------------------------------------------- 1 | #set environment variable RM_INCLUDE_DIR to the location of redismodule.h 2 | ifndef RM_INCLUDE_DIR 3 | RM_INCLUDE_DIR=../ 4 | endif 5 | 6 | ifndef RMUTIL_LIBDIR 7 | RMUTIL_LIBDIR=../rmutil 8 | endif 9 | 10 | # find the OS 11 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 12 | 13 | # Compile flags for linux / osx 14 | ifeq ($(uname_S),Linux) 15 | SHOBJ_CFLAGS ?= -fno-common -g -ggdb 16 | SHOBJ_LDFLAGS ?= -shared -Bsymbolic 17 | else 18 | SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb 19 | SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup 20 | endif 21 | CFLAGS = -I$(RM_INCLUDE_DIR) -Wall -g -fPIC -lc -lm -Og -std=gnu99 22 | CC=gcc 23 | 24 | all: timeseries.so 25 | 26 | timeseries.so: timeseries.o ts_entry.o ts_utils.o timeseries_test.o 27 | echo $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -L../cJSON -lcjson -lc 28 | $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -L../cJSON -lcjson -lc 29 | 30 | clean: 31 | rm -rf *.xo *.so *.o 32 | 33 | -------------------------------------------------------------------------------- /timeseries/timeseries.c: -------------------------------------------------------------------------------- 1 | #include "timeseries.h" 2 | #include "ts_entry.h" 3 | #include "ts_utils.h" 4 | 5 | // TODO README: 6 | // Examples 7 | // TODO Features: 8 | // Configurable timestamp 9 | // Expiration 10 | // Document meta data. Data that is stored on each update. To be used for the 'exactly once' implementation, when streaming data frm kafka. 11 | // interval duration. i.e '10 minute' 12 | // pagination 13 | // TODO Redis questions: 14 | // Persistency and load from disk 15 | // TODO Usability 16 | // Command line help for api 17 | 18 | static char *validIntervals[] = {SECOND, MINUTE, HOUR, DAY, MONTH, YEAR}; 19 | 20 | static RedisModuleType *TSType; 21 | 22 | /** 23 | * TS.CREATEDOC 24 | * example TS.CONF user_report '{ 25 | * "key_fields": ["accountId", "deviceId"], 26 | * "ts_fields": [ "total_amount", "page_views" ], 27 | * "interval": "hour", 28 | * "timeformat": "%Y:%m:%d %H:%M:%S" 29 | * }' 30 | * 31 | * */ 32 | char *ValidateTS(cJSON *conf, cJSON *data) { 33 | int valid, sz, i, esz, j; 34 | 35 | // verify interval parameter 36 | cJSON *interval = VALIDATE_ENUM(conf, interval, validIntervals, "second, minute, hour, day, month, year"); 37 | long timestamp = interval_timestamp(interval->valuestring, cJSON_GetObjectString(data, "timestamp"), 38 | DEFAULT_TIMEFMT); 39 | if (!timestamp) 40 | return "Invalid json: timestamp format and data mismatch"; 41 | 42 | // verify key_fields 43 | cJSON *key_fields = VALIDATE_ARRAY(conf, key_fields); 44 | for (i=0; i < sz; i++) { 45 | cJSON *k = cJSON_GetArrayItem(key_fields, i); 46 | VALIDATE_STRING_TYPE(k); 47 | if (data) { 48 | cJSON *tsfield = cJSON_GetObjectItem(data, k->valuestring); 49 | if (!tsfield) 50 | return "Invalid data: missing field"; 51 | VALIDATE_STRING_TYPE(tsfield); 52 | } 53 | } 54 | 55 | // verify time series fields 56 | cJSON *ts_fields = VALIDATE_ARRAY(conf, ts_fields); 57 | for (i=0; i < sz; i++) { 58 | cJSON *ts_field = cJSON_GetArrayItem(ts_fields, i); 59 | VALIDATE_STRING_TYPE(ts_field); 60 | if (data) { 61 | cJSON *tsfield = cJSON_GetObjectItem(data, ts_field->valuestring); 62 | if (!tsfield) 63 | return "Invalid data: missing field"; 64 | if (tsfield->type != cJSON_Number) 65 | return "Invalid data: agregation field is not a number"; 66 | } 67 | } 68 | 69 | // All is good 70 | return NULL; 71 | } 72 | 73 | int TSCreateDoc(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 74 | void cleanup () {} 75 | if (argc != 3) { 76 | return RedisModule_WrongArity(ctx); 77 | } 78 | RedisModule_AutoMemory(ctx); 79 | 80 | const char *name, *jsonErr, *conf; 81 | cJSON *json; 82 | 83 | name = RedisModule_StringPtrLen(argv[1], NULL); 84 | conf = RedisModule_StringPtrLen(argv[2], NULL); 85 | if (!(json=cJSON_Parse(conf))) 86 | return RedisModule_ReplyWithError(ctx, "Invalid json"); 87 | 88 | if ((jsonErr = ValidateTS(json, NULL))) { 89 | cJSON_Delete(json); 90 | return RedisModule_ReplyWithError(ctx, jsonErr); 91 | } 92 | 93 | RedisModuleCallReply *srep = RedisModule_Call(ctx, "HSET", "ccc", name, name, conf); 94 | // Free the json before assert, otherwise invalid input will cause memory leak of the json 95 | cJSON_Delete(json); 96 | RMUTIL_ASSERT_NOERROR(srep, cleanup); 97 | 98 | RedisModule_ReplyWithSimpleString(ctx, "OK"); 99 | return REDISMODULE_OK; 100 | } 101 | 102 | /* Add new item to array 103 | * Currently increase in 1 item at a time. 104 | * TODO increase array size in chunks 105 | * TODO Handle reached entries limit 106 | * */ 107 | void TSAddItem(struct TSObject *o, double value, time_t timestamp) { 108 | size_t idx = idx_timestamp(o->init_timestamp, timestamp, o->interval); 109 | 110 | if (idx >= o->len) { 111 | size_t newSize = idx + 1; 112 | o->entry = RedisModule_Realloc(o->entry, sizeof(TSEntry) * newSize); 113 | bzero(&o->entry[o->len], sizeof(TSEntry) * (newSize - o->len)); 114 | o->len = newSize; 115 | } 116 | 117 | TSEntry *e = &o->entry[idx]; 118 | e->avg = (e->avg * e->count + value) / (e->count + 1); 119 | e->count++; 120 | } 121 | 122 | int ts_insert(RedisModuleCtx *ctx, RedisModuleString *name, double value, char *timestamp_str) { 123 | RedisModuleKey *key = RedisModule_OpenKey(ctx, name, REDISMODULE_READ|REDISMODULE_WRITE); 124 | 125 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) 126 | return RedisModule_ReplyWithError(ctx, "key doesn't exist"); 127 | if (RedisModule_ModuleTypeGetType(key) != TSType) 128 | return RedisModule_ReplyWithError(ctx, "key is not time series"); 129 | struct TSObject *tso = RedisModule_ModuleTypeGetValue(key); 130 | 131 | time_t timestamp = interval2timestamp(tso->interval, timestamp_str, tso->timefmt); 132 | if (!timestamp) 133 | return RedisModule_ReplyWithError(ctx,"ERR invalid value: Time Stamp is not valid"); 134 | 135 | if (timestamp < tso->init_timestamp) 136 | return RedisModule_ReplyWithError(ctx,"ERR invalid value: Time Stamp is too early"); 137 | /* Add new item */ 138 | TSAddItem(tso, value, timestamp); 139 | RedisModule_ReplyWithSimpleString(ctx, "OK"); 140 | 141 | /* Didn't understand it yet. Just copied from example */ 142 | //RedisModule_ReplicateVerbatim(ctx); 143 | 144 | return REDISMODULE_OK; 145 | } 146 | 147 | int TSInsert(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 148 | RedisModule_AutoMemory(ctx); 149 | 150 | if (argc < 3 || argc > 4) 151 | return RedisModule_WrongArity(ctx); 152 | 153 | double value; 154 | if ((RedisModule_StringToDouble(argv[2],&value) != REDISMODULE_OK)) 155 | return RedisModule_ReplyWithError(ctx,"ERR invalid value: must be a double"); 156 | 157 | return ts_insert(ctx, argv[1], value, argc == 4 ? (char*)RedisModule_StringPtrLen(argv[3], NULL) : NULL); 158 | } 159 | 160 | int ts_create(RedisModuleCtx *ctx, RedisModuleString *name, const char *interval, const char *timefmt, const char *timestamp) { 161 | RedisModuleKey *key = RedisModule_OpenKey(ctx, name, REDISMODULE_READ|REDISMODULE_WRITE); 162 | 163 | if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY) 164 | return RedisModule_ReplyWithError(ctx,"key already exist"); 165 | 166 | struct TSObject *tso = createTSObject(); 167 | RedisModule_ModuleTypeSetValue(key, TSType, tso); 168 | tso->interval = str2interval(interval); 169 | if (tso->interval == none) 170 | return RedisModule_ReplyWithError(ctx,"Invalid interval. Must be one of: second, minute, hour, day, month, year"); 171 | tso->timefmt = timefmt; 172 | tso->init_timestamp = interval_timestamp(interval, timestamp, tso->timefmt); 173 | 174 | return RedisModule_ReplyWithSimpleString(ctx, "OK"); 175 | } 176 | 177 | int TSCreate(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 178 | RedisModule_AutoMemory(ctx); 179 | 180 | if (argc < 3 || argc > 4) 181 | return RedisModule_WrongArity(ctx); 182 | 183 | return ts_create(ctx, argv[1], RedisModule_StringPtrLen(argv[2], NULL), 184 | DEFAULT_TIMEFMT, argc == 4 ? RedisModule_StringPtrLen(argv[3], NULL) : NULL); 185 | } 186 | 187 | int TSInsertDoc(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 188 | cJSON *conf = NULL; 189 | cJSON *data = NULL; 190 | RedisModuleCallReply *confRep = NULL; 191 | const char *jsonErr; 192 | 193 | void cleanup(void) { 194 | cJSON_Delete(data); 195 | cJSON_Delete(conf); 196 | RedisModule_FreeCallReply(confRep); 197 | } 198 | 199 | int exit_status(int status) { 200 | cleanup(); 201 | return status; 202 | } 203 | 204 | if (argc != 3) { 205 | return RedisModule_WrongArity(ctx); 206 | } 207 | RedisModule_AutoMemory(ctx); 208 | 209 | const char *name = RedisModule_StringPtrLen(argv[1], NULL); 210 | 211 | // Time series entry name 212 | confRep = RedisModule_Call(ctx, "HGET", "cc", name, name); 213 | RMUTIL_ASSERT_NONULL(confRep, name, cleanup); 214 | 215 | // Time series entry conf previously stored for 'name' 216 | if (!(conf=cJSON_Parse(RedisModule_CallReplyStringPtr(confRep, NULL)))) 217 | return exit_status(RedisModule_ReplyWithError(ctx, "Something is wrong. Failed to parse ts conf")); 218 | 219 | // Time series entry data 220 | if (!(data=cJSON_Parse(RedisModule_StringPtrLen(argv[2], NULL)))) 221 | return exit_status(RedisModule_ReplyWithError(ctx, "Invalid json")); 222 | 223 | if ((jsonErr = ValidateTS(conf, data))) 224 | return exit_status(RedisModule_ReplyWithError(ctx, jsonErr)); 225 | 226 | // Create timestamp. Use a single timestamp for all entries, not to accidently use different entries in case 227 | // during the calculation the time has changed) 228 | long timestamp = interval_timestamp(cJSON_GetObjectItem(conf, "interval")->valuestring, 229 | cJSON_GetObjectString(data, "timestamp"), DEFAULT_TIMEFMT); 230 | 231 | char *key_prefix = doc_key_prefix(name, conf, data); 232 | 233 | cJSON *ts_fields = cJSON_GetObjectItem(conf, "ts_fields"); 234 | for (int i=0; i < cJSON_GetArraySize(ts_fields); i++) { 235 | char *agg_key = doc_agg_key(key_prefix, cJSON_GetArrayItem(ts_fields, i)); 236 | double value = agg_value(data, cJSON_GetArrayItem(ts_fields, i)); 237 | 238 | char timestamp_key[100]; 239 | sprintf(timestamp_key, "%li", timestamp); 240 | 241 | RedisModuleString *strkey = RedisModule_CreateStringPrintf(ctx, "%s", agg_key); 242 | RedisModuleKey *key = RedisModule_OpenKey(ctx, strkey, REDISMODULE_READ | REDISMODULE_WRITE); 243 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) { 244 | ts_create(ctx, strkey, cJSON_GetObjectItem(conf, "interval")->valuestring, DEFAULT_TIMEFMT, 245 | cJSON_GetObjectString(data, "timestamp")); 246 | } else if (RedisModule_ModuleTypeGetType(key) != TSType) { 247 | return RedisModule_ReplyWithError(ctx, "key is not time series"); 248 | } 249 | RedisModule_CloseKey(key); 250 | 251 | //TODO check errors 252 | ts_insert(ctx, strkey, value, cJSON_GetObjectString(data, "timestamp")); 253 | } 254 | 255 | return exit_status(RedisModule_ReplyWithSimpleString(ctx, "OK")); 256 | } 257 | 258 | int TSGet(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 259 | RedisModule_AutoMemory(ctx); 260 | 261 | if (argc < 3 || argc > 5) 262 | return RedisModule_WrongArity(ctx); 263 | 264 | RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ|REDISMODULE_WRITE); 265 | 266 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) 267 | return RedisModule_ReplyWithError(ctx,"Key doesn't exist"); 268 | 269 | if (RedisModule_ModuleTypeGetType(key) != TSType) 270 | return RedisModule_ReplyWithError(ctx,"Invalid key type"); 271 | 272 | char *op = (char*)RedisModule_StringPtrLen(argv[2], NULL); 273 | struct TSObject *tso = RedisModule_ModuleTypeGetValue(key); 274 | 275 | const char *timestamp = (argc > 3) ? (char*)RedisModule_StringPtrLen(argv[3], NULL) : NULL; 276 | 277 | size_t from = idx_timestamp(tso->init_timestamp, 278 | interval2timestamp(tso->interval, timestamp, tso->timefmt), tso->interval); 279 | 280 | size_t to = (argc < 5) ? from : idx_timestamp(tso->init_timestamp, 281 | interval2timestamp(tso->interval, (char*)RedisModule_StringPtrLen(argv[4], NULL), tso->timefmt), tso->interval); 282 | 283 | if (tso->len <= to) 284 | to = tso->len - 1; 285 | 286 | if (tso->len <= from) { 287 | RedisModuleString *ret = RedisModule_CreateStringPrintf(ctx, 288 | "ERR invalid value: timestamp not exist len: %zu from: %zu to: %zu", tso->len, from, to); 289 | return RedisModule_ReplyWithError(ctx, RedisModule_StringPtrLen(ret, NULL)); 290 | } 291 | 292 | if (to < from) 293 | return RedisModule_ReplyWithError(ctx,"ERR invalid range: end before start"); 294 | 295 | RedisModule_ReplyWithArray(ctx,to - from + 1); 296 | for (size_t i = from; i <= to; i++) { 297 | TSEntry *e = &tso->entry[i]; 298 | if (!strcmp(op, AVG)) 299 | RedisModule_ReplyWithDouble(ctx, e->avg); 300 | else if (!strcmp(op, SUM)) 301 | RedisModule_ReplyWithDouble(ctx, e->avg * e->count); 302 | else if (!strcmp(op, COUNT)) 303 | RedisModule_ReplyWithLongLong(ctx, e->count); 304 | else 305 | return RedisModule_ReplyWithError(ctx,"ERR invalid operation: must be one of avg, sum, count"); 306 | } 307 | 308 | return REDISMODULE_OK; 309 | } 310 | 311 | int TSInfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 312 | RedisModule_AutoMemory(ctx); 313 | char starttimestr[64], endtimestr[64]; 314 | 315 | if (argc != 2) 316 | return RedisModule_WrongArity(ctx); 317 | 318 | RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ|REDISMODULE_WRITE); 319 | 320 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) 321 | return RedisModule_ReplyWithError(ctx,"Key doesn't exist"); 322 | 323 | if (RedisModule_ModuleTypeGetType(key) != TSType) 324 | return RedisModule_ReplyWithError(ctx,"Invalid key type"); 325 | 326 | struct TSObject *tso = RedisModule_ModuleTypeGetValue(key); 327 | 328 | size_t idx = tso->len - (tso->len ? 1 : 0); // Index is len - 1, unless no entries at all. 329 | time_t endtime = tso->init_timestamp + tso->interval * idx; 330 | struct tm st; 331 | 332 | localtime_r(&tso->init_timestamp, &st); 333 | strftime(starttimestr, 64, tso->timefmt, &st); 334 | localtime_r(&endtime, &st); 335 | strftime(endtimestr, 64, tso->timefmt, &st); 336 | 337 | RedisModuleString *ret = RedisModule_CreateStringPrintf(ctx, "Start: %s End: %s len: %zu Interval: %s", 338 | starttimestr, endtimestr, tso->len, interval2str(tso->interval)); 339 | return RedisModule_ReplyWithString(ctx, ret); 340 | 341 | } 342 | 343 | int RedisModule_OnLoad(RedisModuleCtx *ctx) { 344 | // Register the timeseries module itself 345 | if (RedisModule_Init(ctx, "ts", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) 346 | return REDISMODULE_ERR; 347 | 348 | TSType = create_ts_entry_type(ctx); 349 | if (TSType == NULL) return REDISMODULE_ERR; 350 | 351 | // Register timeseries api 352 | RMUtil_RegisterWriteCmd(ctx, "ts.create", TSCreate); 353 | RMUtil_RegisterWriteCmd(ctx, "ts.insert", TSInsert); 354 | RMUtil_RegisterWriteCmd(ctx, "ts.get", TSGet); 355 | RMUtil_RegisterWriteCmd(ctx, "ts.info", TSInfo); 356 | 357 | // Register timeseries doc api 358 | RMUtil_RegisterWriteCmd(ctx, "ts.createdoc", TSCreateDoc); 359 | RMUtil_RegisterWriteCmd(ctx, "ts.insertdoc", TSInsertDoc); 360 | 361 | // register the unit test 362 | RMUtil_RegisterWriteCmd(ctx, "ts.test", TestModule); 363 | 364 | return REDISMODULE_OK; 365 | } 366 | -------------------------------------------------------------------------------- /timeseries/timeseries.h: -------------------------------------------------------------------------------- 1 | #ifndef _TIMESERIES_H_ 2 | #define _TIMESERIES_H_ 3 | 4 | #define _XOPEN_SOURCE // For the use of strptime 5 | #include 6 | 7 | #include 8 | 9 | #include "../redismodule.h" 10 | #include "../rmutil/util.h" 11 | #include "../rmutil/strings.h" 12 | #include "../rmutil/test_util.h" 13 | #include "../cJSON/cJSON.h" 14 | 15 | #define SECOND "second" 16 | #define MINUTE "minute" 17 | #define HOUR "hour" 18 | #define DAY "day" 19 | #define MONTH "month" 20 | #define YEAR "year" 21 | 22 | #define SUM "sum" 23 | #define AVG "avg" 24 | #define COUNT "count" 25 | 26 | #define DEFAULT_TIMEFMT "%Y:%m:%d %H:%M:%S" 27 | 28 | typedef enum { 29 | none = 0, 30 | second = 1, 31 | minute = 60, 32 | hour = 3600, 33 | day = 86400, 34 | month = 2628000, 35 | year = 31536000 36 | } Interval; 37 | 38 | #define TS_MAX_ENTRIES 1000000 39 | 40 | #define RMCALL(reply, call) \ 41 | if (reply) \ 42 | RedisModule_FreeCallReply(reply); \ 43 | reply = call 44 | 45 | #define RMCALL_Assert(reply, call, expr) \ 46 | RMCALL(reply, call); \ 47 | RMUtil_Assert(expr); 48 | 49 | #define RMCALL_AssertNoErr(reply, call) RMCALL_Assert(reply, call, RedisModule_CallReplyType(r) != REDISMODULE_REPLY_ERROR) 50 | 51 | #define VALIDATE_STRING_TYPE(k) \ 52 | if (k->type != cJSON_String) \ 53 | return "Invalid json: key is not a string"; \ 54 | if (!strlen(k->valuestring)) \ 55 | return "Invalid json: empty string is not allowed"; 56 | 57 | #define VALIDATE_KEY(j, key) \ 58 | cJSON_GetObjectItem(j, #key); \ 59 | if (!key) \ 60 | return "Invalid json: missing " #key; 61 | 62 | #define VALIDATE(j, key, Type) \ 63 | VALIDATE_KEY(j, key) \ 64 | if (key->type != Type) \ 65 | return "Invalid json: " #key "is not " #Type; 66 | 67 | #define VALIDATE_STRING(j, key) \ 68 | VALIDATE_KEY(j, key) \ 69 | VALIDATE_STRING_TYPE(key) 70 | 71 | #define VALIDATE_ARRAY(j, key) \ 72 | VALIDATE(j, key, cJSON_Array) \ 73 | sz = cJSON_GetArraySize(key); \ 74 | if (!sz) \ 75 | return "Invalid json: " #key " is empty"; 76 | 77 | #define VALIDATE_ENUM(js, key, validValues, msg) \ 78 | VALIDATE(js, key, cJSON_String) \ 79 | valid = 0; \ 80 | esz = sizeof(validValues) / sizeof(validValues[0]); \ 81 | for (j=0; !valid && j < esz; j++) \ 82 | valid = strcmp( validValues[j], key->valuestring) == 0; \ 83 | if (!valid) \ 84 | return "Invalid json: " #key " is not one of: " msg; 85 | 86 | #define RMUTIL_ASSERT_NONULL(r, entry, cleanup) RMUTIL_ASSERT_NOERROR(r, cleanup) \ 87 | else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_NULL) { \ 88 | char msg[1000] = "No such entry: "; \ 89 | cleanup(); \ 90 | return RedisModule_ReplyWithError(ctx, strcat(msg, entry)); \ 91 | } 92 | 93 | time_t interval_timestamp(const char *interval, const char *timestamp, const char *format); 94 | 95 | size_t idx_timestamp(time_t init_timestamp, size_t cur_timestamp, Interval interval); 96 | 97 | Interval str2interval(const char *interval); 98 | 99 | const char *interval2str(Interval interval); 100 | 101 | // Test function 102 | int TestModule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /timeseries/timeseries_test.c: -------------------------------------------------------------------------------- 1 | #include "timeseries.h" 2 | 3 | char *fmt = DEFAULT_TIMEFMT; 4 | 5 | cJSON *ts_object(char *field, char *agg) { 6 | cJSON *ts = cJSON_CreateObject(); 7 | cJSON_AddStringToObject(ts, "field", field); 8 | cJSON_AddStringToObject(ts, "aggregation", agg); 9 | return ts; 10 | } 11 | 12 | cJSON *testConfBase(cJSON *conf, char *avg, char *interval) { 13 | if (conf) 14 | cJSON_Delete(conf); 15 | 16 | const char *key_fields[] = {"userId", "accountId"}; 17 | const char *ts_fields[] = {"pagesVisited", "storageUsed", "trafficUsed"}; 18 | cJSON *root = cJSON_CreateObject(); 19 | cJSON_AddStringToObject(root, "interval", interval); 20 | cJSON_AddStringToObject(root, "timeformat", fmt); 21 | cJSON_AddItemToObject(root, "key_fields", cJSON_CreateStringArray(key_fields, 2)); 22 | cJSON_AddItemToObject(root, "ts_fields", cJSON_CreateStringArray(ts_fields, 3)); 23 | 24 | return root; 25 | } 26 | 27 | cJSON *testConf(cJSON *conf) { 28 | return testConfBase(conf, AVG, DAY); 29 | } 30 | 31 | cJSON *testConfInvalidInterval(cJSON *conf) { 32 | return testConfBase(conf, DAY, "xxx"); 33 | } 34 | 35 | cJSON *dataJson(double s, double a) { 36 | cJSON *data = cJSON_CreateObject(); 37 | cJSON_AddStringToObject(data, "userId", "userId1"); 38 | cJSON_AddStringToObject(data, "accountId", "accountId1"); 39 | cJSON_AddNumberToObject(data, "pagesVisited", s); 40 | cJSON_AddNumberToObject(data, "storageUsed", 111); 41 | cJSON_AddNumberToObject(data, "trafficUsed", a); 42 | return data; 43 | } 44 | 45 | int testTSApi(RedisModuleCtx *ctx) { 46 | long count; 47 | double val; 48 | char *eptr; 49 | 50 | RedisModuleCallReply *r = NULL; 51 | 52 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "TS.CREATE", "ccc", "tstestapi", "day", "2016:01:01 00:00:00")); 53 | 54 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "TS.INSERT", "ccc", "tstestapi", "10.5", "2016:01:02 00:00:00")); 55 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "TS.INSERT", "ccc", "tstestapi", "11.5", "2016:01:02 00:01:00")); 56 | 57 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "TS.GET", "ccc", "tstestapi", "sum", "2016:01:02 00:01:00")); 58 | count = strtol(RedisModule_CallReplyStringPtr(RedisModule_CallReplyArrayElement(r, 0), NULL), &eptr, 10); 59 | RMUtil_Assert(count == 22); 60 | 61 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "TS.GET", "ccc", "tstestapi", "avg", "2016:01:02 00:01:00")); 62 | val = strtod(RedisModule_CallReplyStringPtr(RedisModule_CallReplyArrayElement(r, 0), NULL), &eptr); 63 | RMUtil_Assert(val == 11); 64 | 65 | return 0; 66 | } 67 | 68 | int testTSAggData(RedisModuleCtx *ctx) { 69 | long timestamp = interval_timestamp(DAY, NULL, NULL); 70 | char timestamp_key[100], count_key[100]; 71 | char *aggdatakey = "aggdata"; 72 | 73 | sprintf(timestamp_key, "%li", timestamp); 74 | sprintf(count_key, "%s:count", timestamp_key); 75 | 76 | RedisModuleCallReply *r = NULL; 77 | cJSON *confJson = NULL, *data1 = dataJson(10.5, 10), *data2 = dataJson(2.5, 20); 78 | 79 | // Remove old data (previous tests) 80 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "DEL", "c", aggdatakey)); 81 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "DEL", "c", "ts.doc:userId1:accountId1:storageUsed:sum")); 82 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "DEL", "c", "ts.doc:userId1:accountId1:trafficUsed:avg")); 83 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "DEL", "c", "ts.doc:userId1:accountId1:pagesVisited:sum")); 84 | // Validate interval values 85 | confJson = testConfInvalidInterval(confJson); 86 | RMCALL(r, RedisModule_Call(ctx, "ts.createdoc", "cc", aggdatakey, cJSON_Print_static(confJson))); 87 | RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR); 88 | RMUtil_Assert(!strcmp(RedisModule_CallReplyStringPtr(r, NULL), 89 | "Invalid json: interval is not one of: second, minute, hour, day, month, year\r\n")); 90 | // Validate add before conf fails 91 | RMCALL(r, RedisModule_Call(ctx, "ts.insertdoc", "cc", aggdatakey, cJSON_Print_static(data1))); 92 | RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR); 93 | // Add conf 94 | confJson = testConf(confJson); 95 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.createdoc", "cc", aggdatakey, cJSON_Print_static(confJson))); 96 | // 1st Add succeed 97 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.insertdoc", "cc", aggdatakey, cJSON_Print_static(data1))); 98 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.insertdoc", "cc", aggdatakey, cJSON_Print_static(data2))); 99 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.insertdoc", "cc", aggdatakey, cJSON_Print_static(data1))); 100 | RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.insertdoc", "cc", aggdatakey, cJSON_Print_static(data1))); 101 | // Verify count is 1 102 | // RMCALL(r, RedisModule_Call(ctx, "HGET", "cc", "ts.agg:userId1:accountId1:pagesVisited:sum", count_key)); 103 | // count = strtol(RedisModule_CallReplyStringPtr(r, NULL), &eptr, 10); 104 | // RMUtil_Assert(count == 1); 105 | // 106 | // RMCALL(r, RedisModule_Call(ctx, "HGET", "cc", "ts.agg:userId1:accountId1:trafficUsed:avg", count_key)); 107 | // count = strtol(RedisModule_CallReplyStringPtr(r, NULL), &eptr, 10); 108 | // RMUtil_Assert(count == 1); 109 | 110 | // Verify value 111 | // RMCALL(r, RedisModule_Call(ctx, "HGET", "cc", "ts.agg:userId1:accountId1:pagesVisited:sum", timestamp_key)); 112 | // val = strtod(RedisModule_CallReplyStringPtr(r, NULL), &eptr); 113 | // RMUtil_Assert(val == 10.5); 114 | // 115 | // RMCALL(r, RedisModule_Call(ctx, "HGET", "cc", "ts.agg:userId1:accountId1:trafficUsed:avg", timestamp_key)); 116 | // val = strtod(RedisModule_CallReplyStringPtr(r, NULL), &eptr); 117 | // RMUtil_Assert(val == 10); 118 | 119 | // 2nd Add 120 | //RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.add", "cc", "testts", cJSON_Print_static(data2))); 121 | 122 | // Verify count is 2 123 | // RMCALL(r, RedisModule_Call(ctx, "HGET", "cc", "ts.agg:userId1:accountId1:pagesVisited:sum", count_key)); 124 | // count = strtol(RedisModule_CallReplyStringPtr(r, NULL), &eptr, 10); 125 | // RMUtil_Assert(count == 2); 126 | // 127 | // // Verify sum value is aggregated 128 | // RMCALL(r, RedisModule_Call(ctx, "HGET", "cc", "ts.agg:userId1:accountId1:pagesVisited:sum", timestamp_key)); 129 | // val = strtod(RedisModule_CallReplyStringPtr(r, NULL), &eptr); 130 | // RMUtil_Assert(val == 13); 131 | // 132 | // // Verify avg value is aggregated 133 | // RMCALL(r, RedisModule_Call(ctx, "HGET", "cc", "ts.agg:userId1:accountId1:trafficUsed:avg", timestamp_key)); 134 | // val = strtod(RedisModule_CallReplyStringPtr(r, NULL), &eptr); 135 | // RMUtil_Assert(val == 15); 136 | // 137 | // // Add with timestamp 138 | // cJSON_AddStringToObject(data2, "timestamp", "2016:10:05 06:40:01"); 139 | // RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.add", "cc", "testts", cJSON_Print_static(data2))); 140 | // 141 | // // Add with wrong timestamp 142 | // cJSON_AddStringToObject(data1, "timestamp", "2016-10-05 06:40:01"); 143 | // //RMCALL_AssertNoErr(r, RedisModule_Call(ctx, "ts.add", "cc", "testts", cJSON_Print_static(data1))); 144 | // RMCALL(r, RedisModule_Call(ctx, "ts.add", "cc", "testts", cJSON_Print_static(data1))); 145 | // RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR); 146 | // RMUtil_Assert(!strcmp(RedisModule_CallReplyStringPtr(r, NULL), 147 | // "Invalid json: timestamp format and data mismatch\r\n")); 148 | 149 | cJSON_Delete(data1); 150 | cJSON_Delete(data2); 151 | cJSON_Delete(confJson); 152 | RedisModule_FreeCallReply(r); 153 | return 0; 154 | } 155 | 156 | #define EQ(interval, t1, t2) \ 157 | RMUtil_Assert( interval_timestamp(interval, t1, fmt) == interval_timestamp(interval, t2, fmt)) 158 | 159 | #define NEQ(interval, t1, t2) \ 160 | RMUtil_Assert( interval_timestamp(interval, t1, fmt) != interval_timestamp(interval, t2, fmt)) 161 | 162 | int testTimeInterval(RedisModuleCtx *ctx) { 163 | EQ (SECOND, "2016:11:05 06:40:00.001", "2016:11:05 06:40:00.002"); 164 | NEQ (SECOND, "2016:11:05 06:40:01", "2016:11:05 06:40:02"); 165 | 166 | EQ (MINUTE, "2016:11:05 06:40:01", "2016:11:05 06:40:02"); 167 | NEQ (MINUTE, "2016:11:05 06:41:01", "2016:11:05 06:42:02"); 168 | 169 | EQ (HOUR, "2016:11:05 06:41:01", "2016:11:05 06:42:02"); 170 | NEQ (HOUR, "2016:11:05 07:41:01", "2016:11:05 06:42:02"); 171 | 172 | EQ (DAY, "2016:11:05 07:41:01", "2016:11:05 06:42:02"); 173 | NEQ (DAY, "2016:11:06 07:41:01", "2016:11:05 06:42:02"); 174 | 175 | EQ (MONTH, "2016:11:06 07:41:01", "2016:11:05 06:42:02"); 176 | NEQ (MONTH, "2016:10:06 07:41:01", "2016:11:05 06:42:02"); 177 | 178 | EQ (YEAR, "2016:10:06 07:41:01", "2016:11:05 06:42:02"); 179 | NEQ (YEAR, "2016:10:06 07:41:01", "2015:11:05 06:42:02"); 180 | 181 | // Wrong format 182 | RMUtil_Assert(interval_timestamp(DAY, "2016-10-06 07:41:01", fmt) == 0) 183 | 184 | return 0; 185 | } 186 | 187 | #define IDX(interval, t1, t2, idx) RMUtil_Assert( idx_timestamp( \ 188 | interval_timestamp(interval, t1, fmt), interval_timestamp(interval, t2, fmt), str2interval(interval)) == idx) 189 | 190 | #define IDXGT(interval, t1, t2, idx) RMUtil_Assert( idx_timestamp( \ 191 | interval_timestamp(interval, t1, fmt), interval_timestamp(interval, t2, fmt), str2interval(interval)) > idx) 192 | 193 | int testTimestampIdx(RedisModuleCtx *ctx) { 194 | 195 | IDX (SECOND, "2016:11:05 06:40:00.001", "2016:11:05 06:40:00.002", 0); 196 | 197 | IDX (SECOND, "2016:11:05 06:40:01", "2016:11:05 06:40:02", 1); 198 | IDX (SECOND, "2016:11:05 06:40:01", "2016:11:05 06:40:03", 2); 199 | IDX (SECOND, "2016:11:05 06:40:01", "2016:11:06 06:40:03", 86402); 200 | 201 | IDX (MINUTE, "2016:11:05 06:40:01", "2016:11:05 06:40:02", 0); 202 | IDX (MINUTE, "2016:11:05 06:41:01", "2016:11:05 06:42:02", 1); 203 | 204 | IDX (HOUR, "2016:11:05 06:41:01", "2016:11:05 06:42:02", 0); 205 | IDX (HOUR, "2016:11:05 07:41:01", "2016:11:05 08:42:02", 1); 206 | 207 | IDX (DAY, "2016:11:05 07:41:01", "2016:11:05 06:42:02", 0); 208 | IDX (DAY, "2016:11:06 07:41:01", "2016:11:07 06:42:02", 1); 209 | IDX (DAY, "2016:11:06 07:41:01", "2016:11:07 06:42:02", 1); 210 | 211 | IDX (MONTH, "2016:11:06 07:41:01", "2016:11:05 06:42:02", 0); 212 | IDX (MONTH, "2016:10:06 07:41:01", "2016:11:05 06:42:02", 1); 213 | 214 | IDX (YEAR, "2016:10:06 07:41:01", "2016:11:05 06:42:02", 0); 215 | IDX (YEAR, "2015:10:06 07:41:01", "2016:11:05 06:42:02", 1); 216 | 217 | IDX (YEAR, "2016:10:06 07:41:01", "2015:11:05 06:42:02", -1); 218 | 219 | IDXGT (YEAR, "2016:10:06 07:41:01", "2015:11:05 06:42:02", TS_MAX_ENTRIES); 220 | 221 | return 0; 222 | } 223 | 224 | // Unit test entry point for the timeseries module 225 | int TestModule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 226 | RedisModule_AutoMemory(ctx); 227 | 228 | RMUtil_Test(testTSApi); 229 | 230 | RMUtil_Test(testTimeInterval); 231 | 232 | RMUtil_Test(testTimestampIdx); 233 | 234 | RMUtil_Test(testTSAggData); 235 | 236 | RedisModule_ReplyWithSimpleString(ctx, "PASS"); 237 | return REDISMODULE_OK; 238 | } 239 | -------------------------------------------------------------------------------- /timeseries/ts_conf.c: -------------------------------------------------------------------------------- 1 | /* ========================== "tsconfigr" type methods ======================= */ 2 | // name, interval, key fields, agg fields, timefmt (keep original, metadata) 3 | 4 | static RedisModuleType *TSConfig; 5 | 6 | typedef struct TSConfigObject { 7 | const char *name; 8 | Interval interval; 9 | const char **key_fields; 10 | size_t key_fields_length; 11 | const char **agg_fields; 12 | size_t agg_fields_length; 13 | const char **metadata_fields; 14 | size_t metadata_fields_length; 15 | const char *timefmt; 16 | } TSConfigObject; 17 | 18 | struct TSConfigObject *createTSConfigObject(void) { 19 | struct TSConfigObject *o; 20 | o = RedisModule_Calloc(1, sizeof(*o)); 21 | return o; 22 | } 23 | 24 | void TSConfigReleaseObject(struct TSConfigObject *o) { 25 | RedisModule_Free(o->name); 26 | for (int i = 0; i < o->key_fields_length; i++) RedisModule_Free(o->key_fields[i]); 27 | for (int i = 0; i < o->agg_fields_length; i++) RedisModule_Free(o->agg_fields[i]); 28 | for (int i = 0; i < o->metadata_fields_length; i++) RedisModule_Free(o->metadata_fields[i]); 29 | RedisModule_Free(o->timefmt); 30 | RedisModule_Free(o); 31 | } 32 | 33 | void *TSRdbLoad(RedisModuleIO *rdb, int encver) { 34 | if (encver != 0) { 35 | /* RedisModule_Log("warning","Can't load data with version %d", encver);*/ 36 | return NULL; 37 | } 38 | 39 | struct TSObject *tso = createTSObject(); 40 | tso->len = RedisModule_LoadUnsigned(rdb); 41 | size_t len = 0; 42 | if (tso->len) 43 | tso->entry = (TSEntry *)RedisModule_LoadStringBuffer(rdb, &len); 44 | 45 | return tso; 46 | } 47 | 48 | void TSRdbSave(RedisModuleIO *rdb, void *value) { 49 | struct TSObject *tso = value; 50 | RedisModule_SaveUnsigned(rdb,tso->len); 51 | if (tso->len) 52 | RedisModule_SaveStringBuffer(rdb,(const char *)tso->entry,tso->len * sizeof(double)); 53 | } 54 | 55 | void TSAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) { 56 | struct TSObject *tso = value; 57 | if(tso->entry) { 58 | RedisModule_EmitAOF(aof,"TS.INSERT","sc",key,tso->entry); 59 | } 60 | } 61 | 62 | void TSDigest(RedisModuleDigest *digest, void *value) { 63 | /* TODO: The DIGEST module interface is yet not implemented. */ 64 | } 65 | 66 | void TSFree(void *value) { 67 | TSReleaseObject(value); 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /timeseries/ts_entry.c: -------------------------------------------------------------------------------- 1 | #include "ts_entry.h" 2 | 3 | struct TSObject *createTSObject(void) { 4 | struct TSObject *o; 5 | o = RedisModule_Alloc(sizeof(*o)); 6 | o->entry = NULL; 7 | o->len = 0; 8 | return o; 9 | } 10 | 11 | void TSReleaseObject(struct TSObject *o) { 12 | RedisModule_Free(o->entry); 13 | RedisModule_Free(o); 14 | } 15 | 16 | void *TSRdbLoad(RedisModuleIO *rdb, int encver) { 17 | if (encver != 0) { 18 | /* RedisModule_Log("warning","Can't load data with version %d", encver);*/ 19 | return NULL; 20 | } 21 | 22 | struct TSObject *tso = createTSObject(); 23 | tso->len = RedisModule_LoadUnsigned(rdb); 24 | size_t len = 0; 25 | if (tso->len) 26 | tso->entry = (TSEntry *)RedisModule_LoadStringBuffer(rdb, &len); 27 | 28 | return tso; 29 | } 30 | 31 | void TSRdbSave(RedisModuleIO *rdb, void *value) { 32 | struct TSObject *tso = value; 33 | RedisModule_SaveUnsigned(rdb,tso->len); 34 | if (tso->len) 35 | RedisModule_SaveStringBuffer(rdb,(const char *)tso->entry,tso->len * sizeof(double)); 36 | } 37 | 38 | void TSAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) { 39 | struct TSObject *tso = value; 40 | if(tso->entry) { 41 | RedisModule_EmitAOF(aof,"TS.INSERT","sc",key,tso->entry); 42 | } 43 | } 44 | 45 | void TSDigest(RedisModuleDigest *digest, void *value) { 46 | /* TODO: The DIGEST module interface is yet not implemented. */ 47 | } 48 | 49 | void TSFree(void *value) { 50 | TSReleaseObject(value); 51 | } 52 | 53 | RedisModuleType *create_ts_entry_type(RedisModuleCtx *ctx) { 54 | /* Name must be 9 chars... */ 55 | return RedisModule_CreateDataType(ctx, "timeserie", 0, TSRdbLoad, TSRdbSave, TSAofRewrite, TSDigest, TSFree); 56 | } -------------------------------------------------------------------------------- /timeseries/ts_entry.h: -------------------------------------------------------------------------------- 1 | #ifndef _TS_ENTRY_ 2 | #define _TS_ENTRY_ 3 | 4 | #include "timeseries.h" 5 | 6 | typedef struct TSEntry { 7 | unsigned short count; 8 | double avg; 9 | }TSEntry; 10 | 11 | typedef struct TSObject { 12 | TSEntry *entry; 13 | size_t len; 14 | time_t init_timestamp; 15 | Interval interval; 16 | const char *timefmt; 17 | }TSObject; 18 | 19 | struct TSObject *createTSObject(void); 20 | 21 | RedisModuleType *create_ts_entry_type(RedisModuleCtx *ctx); 22 | 23 | #endif -------------------------------------------------------------------------------- /timeseries/ts_utils.c: -------------------------------------------------------------------------------- 1 | #include "ts_entry.h" 2 | 3 | time_t interval2timestamp(Interval interval, const char *timestamp, const char *format) { 4 | return interval_timestamp(interval2str(interval), timestamp, format); 5 | } 6 | 7 | time_t interval_timestamp(const char *interval, const char *timestamp, const char *format) { 8 | struct tm st; 9 | memset(&st, 0, sizeof(struct tm)); 10 | if (timestamp && format) { 11 | if (!strptime(timestamp, format, &st)) 12 | return 0; 13 | } 14 | else { 15 | time_t t = time(NULL); 16 | gmtime_r(&t, &st); 17 | } 18 | 19 | if (!strcmp(interval, SECOND)) return mktime(&st); 20 | st.tm_sec = 0; if (!strcmp(interval, MINUTE)) return mktime(&st); 21 | st.tm_min = 0; if (!strcmp(interval, HOUR)) return mktime(&st); 22 | st.tm_hour = 0; if (!strcmp(interval, DAY)) return mktime(&st); 23 | st.tm_mday = 0; if (!strcmp(interval, MONTH)) return mktime(&st); 24 | st.tm_mon = 0; 25 | return mktime(&st); 26 | } 27 | 28 | Interval str2interval(const char *interval) { 29 | if (!strcmp(SECOND, interval)) return second; 30 | if (!strcmp(MINUTE, interval)) return minute; 31 | if (!strcmp(HOUR, interval)) return hour; 32 | if (!strcmp(DAY, interval)) return day; 33 | if (!strcmp(MONTH, interval)) return month; 34 | if (!strcmp(YEAR, interval)) return year; 35 | 36 | return none; 37 | } 38 | 39 | const char *interval2str(Interval interval) { 40 | if (interval == second) return SECOND; 41 | if (interval == minute) return MINUTE; 42 | if (interval == hour) return HOUR; 43 | if (interval == day) return DAY; 44 | if (interval == month) return MONTH; 45 | if (interval == year) return YEAR; 46 | 47 | return none; 48 | } 49 | 50 | size_t idx_timestamp(time_t init_timestamp, size_t cur_timestamp, Interval interval) { 51 | return difftime(cur_timestamp, init_timestamp) / interval; 52 | } 53 | 54 | char *doc_key_prefix(const char *name, cJSON *conf, cJSON *data) 55 | { 56 | static char key_prefix[1000] = ""; 57 | strcpy(key_prefix, name); 58 | strcat(key_prefix, ":"); 59 | cJSON *key_fields = cJSON_GetObjectItem(conf, "key_fields"); 60 | for (int i=0; i < cJSON_GetArraySize(key_fields); i++) { 61 | cJSON *k = cJSON_GetArrayItem(key_fields, i); 62 | cJSON *d = cJSON_GetObjectItem(data, k->valuestring); 63 | strcat(key_prefix, d->valuestring); 64 | strcat(key_prefix, ":"); 65 | } 66 | return key_prefix; 67 | } 68 | 69 | char *doc_agg_key(char *key_prefix, cJSON *ts_field){ 70 | static char agg_key[1000]; 71 | sprintf(agg_key, "%s%s", key_prefix, ts_field->valuestring); 72 | return agg_key; 73 | } 74 | 75 | double agg_value(cJSON *data, cJSON *ts_field) { 76 | return cJSON_GetObjectItem(data, ts_field->valuestring)->valuedouble; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /timeseries/ts_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _TS_UTILS_H_ 2 | #define _TS_UTILS_H_ 3 | 4 | #include "timeseries.h" 5 | #include "ts_entry.h" 6 | 7 | time_t interval2timestamp(Interval interval, const char *timestamp, const char *format); 8 | 9 | time_t interval_timestamp(const char *interval, const char *timestamp, const char *format); 10 | 11 | Interval str2interval(const char *interval); 12 | 13 | const char *interval2str(Interval interval); 14 | 15 | size_t idx_timestamp(time_t init_timestamp, size_t cur_timestamp, Interval interval); 16 | 17 | char *doc_key_prefix(const char *name, cJSON *conf, cJSON *data); 18 | 19 | char *doc_agg_key(char *key_prefix, cJSON *ts_field); 20 | 21 | double agg_value(cJSON *data, cJSON *ts_field); 22 | 23 | #endif --------------------------------------------------------------------------------