├── .gitignore ├── LICENSE.md ├── README.md ├── config ├── generate-config.sh ├── sys.config └── vm.args ├── elvis.config ├── example ├── config │ ├── config.vars │ ├── generate-config.sh │ ├── template.sys.config │ └── vm.args ├── elvis.config ├── rebar.config ├── rebar.config.script ├── relx.config └── src │ ├── em.app.src │ ├── em.erl │ └── em_service.erl ├── generic_app ├── config │ ├── .gitignore │ ├── config.vars │ ├── generate-config.sh │ ├── sys.config │ ├── template.sys.config │ └── vm.args ├── rebar.config ├── rebar.config.script ├── relx.config └── src │ ├── app.app.src │ ├── app.erl │ └── app_service.erl ├── include └── csi.hrl ├── rebar.config ├── rebar.config.script ├── src ├── csi.app.src ├── csi.erl ├── csi_app.erl ├── csi_common.hrl ├── csi_server.erl ├── csi_service.erl ├── csi_stats.erl ├── csi_sup.erl └── csi_utils.erl ├── test ├── .gitignore └── csi_SUITE.erl └── wombat └── wombat_plugin_csi.erl /.gitignore: -------------------------------------------------------------------------------- 1 | /ebin/ 2 | /deps/ 3 | /_rel/ 4 | /doc/ 5 | /relx 6 | /log/ 7 | /scripts/ 8 | /.settings/ 9 | /.project 10 | /_build/ 11 | /logs/ 12 | /csi.d 13 | /rebar.lock 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CommonServiceInterface 2 | 3 | # ALPHA VERSION USE WITH CARE 4 | 5 | ## Build environment 6 | In case you use erlang.mk and relx the Quick start section contains the necessary setup. Otherwise just make sure the dependency on csi is included in your build environment 7 | 8 | ## Quick start on using CSI with rebar3 9 | 10 | In your application root directory, make sure the following are inserted into your rebar.config under the deps key 11 | 12 | {deps, [ 13 | {csi, {git, "git@github.com:esl/CommonServiceInterface.git", {branch, "master"}}} 14 | ]}. 15 | 16 | Include csi in your relx config part of rebar.config, similar to this: 17 | 18 | {release, {your_server, "0.0.1"}, 19 | [csi, 20 | your_server, 21 | runtime_tools]}. 22 | {extend_start_script, true}. 23 | 24 | In your app.src file, include csi in the applications section. 25 | 26 | ## Quick start on using CSI with erlang.mk 27 | In your application root directory, make sure the following are inserted into your Makefile 28 | 29 | PROJECT = yourapplicationname 30 | DEPS = csi 31 | 32 | dep_csi = git git@github.com:esl/CommonServiceInterface.git master 33 | 34 | include erlang.mk 35 | 36 | # Compile flags 37 | # if you want to have lager used by CSI include -Dlager 38 | # 39 | ERLC_COMPILE_OPTS += -Dlager 40 | 41 | # Use the same settings for compiling releases as well as for testing 42 | ERLC_OPTS += $(ERLC_COMPILE_OPTS) 43 | TEST_ERLC_OPTS += $(ERLC_COMPILE_OPTS) 44 | 45 | Include csi in your relx.config, similar to this: 46 | 47 | {release, {your_server, "0.0.1"}, 48 | [csi, 49 | your_server, 50 | runtime_tools]}. 51 | {extend_start_script, true}. 52 | 53 | In your app.src file, include csi in the applications section. 54 | 55 | $>make run 56 | 57 | You will have csi included in your application from now on. 58 | 59 | ## Recommended configuration layout 60 | 61 | In the csi example application you can see what is the recommended configuration layout of Erlang applications here. In the config/generate-config.sh you can see the roles of different files. 62 | 63 | In general `sys.config` will be generated from `template.sys.config` using config vars from `config.vars`. In `config.vars` there are different section for build environments. In the lack of template file, just sys.config will be used, so templating is optional. In addition you can use `override.vars` to overwrite specific config element for any application (dependencies or the main application). An example for `override.vars` 64 | 65 | ```erlang 66 | {dev, [ 67 | %% To set or replace the hwm of lager 68 | {"lager.error_logger_hwm", 500}, 69 | %% To remove this key from the config term 70 | "lager.async_threshold_window" 71 | ]}. 72 | {prod, [ 73 | {"lager.log_root", "/var/log/stats-feed/log"} 74 | ]}. 75 | ``` 76 | String paths are converted to atom list so only config values referenced by atoms can be manipulated. 77 | 78 | ## Managing timeout values 79 | 80 | Calling a CSI server to process a request in parallel, needs two different timeout values to be used. Client Timeout is the time spent waiting for the server to return, Server Timeout corresponds to the request being processed. 81 | 82 | It is important to have Client Timeout greater than the Server Timeout, since a blocking call shall not be returning before the request has been processed. Otherwise if the Client Timeout is smaller than the Server Timeout, the caller may get a timeout message before the request could be processed in a wider time frame, thus there will be unwanted messages in the client's mailbox when the processed request sends the result back. 83 | 84 | By default, the Client Timeout is set to infinity and the Server Timeout is set to 55 seconds. 85 | 86 | In case you need to change these values, you have three options: 87 | 88 | 1. During initialisation. 89 | When a CSI server is launched, you can append a property list to set the server's parameters. [{server_timeout, Value}] passed as the last argument to start() or start link() will set the Server Timeout to be used by the launching server. 90 | 2. At runtime. 91 | With csi:set_options(ServerName :: atom(), Options :: property_list()) you can set the Server Timeout at runtime. 92 | 3. At a specific call 93 | Calls to CSI service server goes through the the service API, where you can append the one-time Server Timeout and also the Client Timeout values. Please visit csi.erl for details. 94 | 95 | When changing the values, the timeout value parameters are in milliseconds. 96 | 97 | ## Quick Start with the example 98 | Clone the repository, go to its directory. 99 | 100 | $>cd example 101 | 102 | In case you use rebar3, issue the following command: 103 | 104 | $>rebar3 shell 105 | 106 | In case you use erlang.mk, issue the following command: 107 | 108 | $>make run 109 | 110 | You will have an erlang shell. Here is how to play with it: 111 | 112 | 1> em:start(). 113 | {ok,<0.83.0>} 114 | 2> em: 115 | module_info/0 module_info/1 process_crashing/1 116 | process_foo/1 process_too_long/1 start/0 117 | start_link/0 stop/0 118 | 2> em:process_foo(test). 119 | hello_world 120 | 3> em:process_too_long(test). 121 | {error,timeout_killed} 122 | 4> em:process_crashing(test). 123 | {error,exception} 124 | 5> 19:36:30.489 [error] Exception in service when calling em_service:process_crashing([test]). error:badarith. Stacktrace:[{em_service,process_crashing,2,[{file,"src/em_service.erl"},{line,46}]},{csi_server,process_service_request,8,[{file,"src/csi_server.erl"},{line,402}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,237}]}] 125 | 19:36:30.490 [error] CRASH REPORT Process <0.89.0> with 0 neighbours exited with reason: exception in csi_server:process_service_request/8 line 425 126 | 127 | 5> csi:stats_get_all(em_service). 128 | [{{response_time,process_foo},{1,106,106.0,106,106}}] 129 | (em_server@127.0.0.1)6> csi:services 130 | services/0 services_status/0 131 | 6> csi:services_status(). 132 | [{{registered_name,em_service}, 133 | {csi_service_state,em_service,em_service, 134 | {em_state}, 135 | true,36888,40985,csi_stats, 136 | [all], 137 | [], 138 | [{response_time,[{"last_nth_to_collect",10}, 139 | {"normalize_to_nth",8}]}]}}, 140 | {{registered_name,csi_service}, 141 | {csi_service_state,csi_service,csi_service, 142 | {csi_service_state}, 143 | true,20500,24597,csi_stats, 144 | [all], 145 | [], 146 | [{response_time,[{"last_nth_to_collect",10}, 147 | {"normalize_to_nth",8}]}]}}] 148 | 7> em:stop(). 149 | ok 150 | 8> 151 | 152 | And if you use lager, here is what you find in the console.log after the sequence above: 153 | 154 | 2016-05-02 11:31:05.432 [info] <0.6.0> Application csi started on node nonode@nohost 155 | 2016-05-02 11:31:54.417 [info] <0.6.0> Application em started on node nonode@nohost 156 | 2016-05-02 13:52:49.517 [info] <0.6.0> Application lager started on node nonode@nohost 157 | 2016-05-02 13:52:49.529 [info] <0.6.0> Application csi started on node nonode@nohost 158 | 2016-05-02 13:53:36.009 [error] <0.146.0> CRASH REPORT Process <0.146.0> with 0 neighbours exited with reason: exception in 159 | csi_server:process_service_request/8 line 456 160 | 161 | # Road to use CSI 162 | 163 | ## Introduction 164 | In an Erlang application, usually we have a number of services communicating themselves to fulfill the incoming requests from the clients. These services are implemented by writing gen_server code most of the time. Some of them may be processor intensive, others could be I/O intensive. When we face to a situation that our system slows down, it is hard to find the root cause of the performance degradation. We may have some clue, which service (or gen_server) takes the resources causing the bottleneck, on the other hand if we were able to measure basic metrics for all the services, the quest for the root cause would turn to be much easier. 165 | 166 | Implementing the metrics in each and every service takes time and have the risk of non-conformity as different developers may have different ways and ideas on how to implement a common metric. 167 | 168 | If we had a generalized gen_server that all the services comply with, implementing common behaviors for all services would mean to code only once and use it as many times as the number of services we would like to have it implemented. 169 | 170 | So instead of writing a gen_server for a service, we can use a generalized gen_server, to handle the request by making calls into our callback module that implements the functionalities. 171 | 172 | ## Service as a service 173 | Using the generalized gen_server, the service we need to implement can focus on the business logic. The service is wrapped around with the common code that handles the requests and provides the return value back to the requester. 174 | 175 | This generalized gen_server is also a service by itself. It's functionality is to handle the requests, make them concurrently processed if it is needed, and also take measurements and common service behaviours. In addition, It shall keep itself open for future improvements. 176 | 177 | ## The Common Service Interface (CSI) 178 | In short, CSI sits between the service API and the service logic. When a request is coming by calling the service API, the API shall call CSI and tell it how the request shall be processed. Then CSI takes the order and makes the call into the service logic (a callback module), and responds to the caller with the value the service logic gives back. 179 | 180 | So we have two interfaces: 181 | 182 | 1. The way to handle incoming requests. 183 | 2. The behaviour of the callback module where the requests are processed. 184 | 185 | ### Handling incoming requests 186 | The call flow for any incoming request hitting the service is the following. The caller executes a servicename:function(Parameters) call in order to have a result from the service. In the servicename.erl file - which is the API for the service - all function have the same format as to call the csi:calltype(Parameters) API of the CSI server. There are a couple of call types available that are described below. 187 | 188 | For a given service, this is all what is needed to set up the API. 189 | 190 | ### Processing the request in the callback module 191 | 192 | As the CSI server is the gen_server for the implemented service, it handles the call made in the service.erl API in it's handle_call function, by calling the callback module's function(Parameters). The callback module shall return the Results to the CSI gen_server that will forward it back to the original requester. As a practice the module name shall be servicename_services.erl 193 | 194 | ## A tiny example. 195 | In order to quickly understand the usage of CSI, we will implement a small service, called em_service that provides three functionalities: 196 | 197 | 1. process_foo(Atom). - Returns the atom hello_world. 198 | 2. process_too_long(Atom). - Sleeps for a long time to test the timeout kill functionality 199 | 3. process_crashing(Atom). - Throws an exception. 200 | 201 | As you will see, there are two main parts of our em_service.erl callback module: 202 | 203 | ### 1. Behavioural functions 204 | Implementing a service needs some housekeeping. When the service server is launched through a start() or start_link(), the init_service() function is called in the callback module. Here, the service shall initialize it's global state and return with an {ok,ServiceState} tuple. This ServiceState is used to pass the global state when starting to process each and every request. 205 | 206 | terminate_service() is the counterpart of init_service(). It is called when the service is being terminated, so this is the last place where the cleanups shall be executed before a service shuts down. 207 | 208 | When a request reaches the server, it calls the callback module's init(Args,ServiceState) function to initialize the processing thread. The Args parameter is the same as the service request was called with. It shall return with {ok,RequestState} that is used during the processing of a specific request. Here a DB connection could be taken from a pool for example and be passed back as part of the RequestState. 209 | 210 | When init returned with {ok,RequestState} our CSI gen_server calls the function that will process the request. 211 | 212 | When the request is processed, our gen_server finally calls terminate(Reason,RequestState) callback function to have a cleanup after processing a single request. 213 | 214 | These are the functions that can be found in the second part of the callback module, and called as Service Functions. 215 | 216 | ### 2. Service functions 217 | 218 | All service functions have two arguments. The parameter passed when the function was called (can be a list or tuple in case more than one parameter is needed) and the state of the request processing that was initialized during the init call. 219 | 220 | The implementation for the service functions goes to em_service.erl: 221 | ```erlang 222 | -module(em_service). 223 | -behaviour(csi_server). 224 | 225 | %% General state of the service 226 | -record(em_state,{}). 227 | 228 | %% Lifecycle State for every requests' 229 | -record(em_session_state,{}). 230 | 231 | -export([init_service/1, 232 | init/2, 233 | terminate/2, 234 | terminate_service/2]). 235 | 236 | -export([process_foo/2, 237 | process_too_long/2, 238 | process_crashing/2]). 239 | 240 | 241 | %% ==================================================================== 242 | %% Behavioural functions 243 | %% ==================================================================== 244 | init_service(_InitArgs) -> 245 | {ok,#em_state{}}. 246 | 247 | init(_Args,_ServiceState) -> 248 | {ok,#em_session_state{}}. 249 | 250 | terminate(_Reason,_State) -> 251 | ok. 252 | 253 | terminate_service(_Reason,_State) -> 254 | ok. 255 | 256 | %% ==================================================================== 257 | %% Service functions 258 | %% ==================================================================== 259 | process_foo(_Args,State) -> 260 | {hello_world,State}. 261 | 262 | process_too_long(_Args,State) -> 263 | timer:sleep(100000), 264 | {long_job_fininshed,State}. 265 | 266 | process_crashing(Args,State) -> 267 | A = Args - Args, 268 | {A,State}. 269 | ``` 270 | 271 | The service functions shall return a tuple containing {Result, NewState}. 272 | 273 | So far we have the business logic implemented. Let us see, how to create the API for the service 274 | 275 | ### Service API 276 | The entry points to a service is very similar to a gen_server implementation. There are a couple of housekeeping functions like start, start_link, stop and then the exposed functionality is declared. 277 | 278 | When launching a service, it's start or start_link function shall be called. As our em service uses the CSI gen server, it needs to tell the CSI server, on what name the service shall be registered locally and also what the module is that implements the callback functions along with the functionality for the service. So start and start_link have two parameters, the name of the service and the service module. In our case the latter is em_service.erl as created above. 279 | 280 | The business logic is called through the CSI server, by csi:call_p(?SERVICE_NAME,function,Args). What happens then, is the CSI server code calls your service module function to perform the operation. You might have recognized, we used call_p instead of the simple call(). There are several ways a server can process a request. It can do parallel or serialized request processing. When we use call_p() the CSI server spawns a separate process for handling the request. In this process, the callback module's init(), function() and terminate() will be called as described above. call_p() is to be used for concurrent request processing. call_s() is similar, but no spawned process will handle the request. It means, the requests are serialized so handled one by one. Both ways, the requester is blocked as long as the callback function returns. 281 | 282 | There are a number of other ways exist to call a service, here I would mention just one additionally. If instead of call_p() we use post_p() it will immediately return with a tuple containing the Pid and a Reference of the process spawned for the request and later the result will be sent back to the requester in an Erlang message. 283 | 284 | Here we will take a look at call_p for simplicity. 285 | ```erlang 286 | -module(em). 287 | 288 | -define(SERVICE_NAME,em_service). 289 | -define(SERVICE_MODULE,em_service). 290 | 291 | %% ==================================================================== 292 | %% API functions 293 | %% ==================================================================== 294 | -export([start/0, 295 | start_link/0, 296 | stop/0]). 297 | 298 | -export([process_foo/1, 299 | process_too_long/1, 300 | process_crashing/1]). 301 | 302 | 303 | start() -> csi:start(?SERVICE_NAME,?SERVICE_MODULE). 304 | start_link() -> csi:start_link(?SERVICE_NAME,?SERVICE_MODULE). 305 | 306 | stop() -> csi:stop(?SERVICE_NAME). 307 | 308 | process_foo(Atom) -> csi:call_p(?SERVICE_NAME,process_foo,[Atom]). 309 | process_too_long(Atom) -> csi:call_p(?SERVICE_NAME,process_too_long,[Atom]). 310 | process_crashing(Atom) -> csi:call_p(?SERVICE_NAME,process_crashing,[Atom]). 311 | ``` 312 | 313 | For a more complex service, you may want to look at csi.erl for the API of CSI itself, csi_service.erl to have some feeling how a more complex service is implemented or csi_server.erl where all the magic happens. 314 | 315 | ## Extending CSI functionality 316 | It is easy to add new functionalities to services servers if they use the common CSI framework. The only place we need to implement it is the CSI itself and all services using it will have that new functionality. 317 | 318 | # Statistic functionality 319 | CSI implements a statistical framework. When a request is get processed, CSI calls all the statistics that was given to it through its csi:stats_include_type(service_name,stat_type) API, where stat_type is the name of the function in the given statistics module (default is csi_stats.erl). The stats module shall implement the functions for the types. For example if there is a stat_type named response_time, there shall be a corresponding function in the stats module with the following header: 320 | 321 | response_time(Stage, Request, FullRequest, Ref, Params, Tab, TempTab, Timestamp) -> 322 | 323 | Where the arguments are the following: 324 | 325 | - Stage. There are three possible values the Stage can have: 326 | 1. start. The processing of a request is about to be started. 327 | 2. stop. The processing of the request has finished 328 | 3. clean. There was some problem and the request processing shall not be measured, so clean up if there was something set up at start and no longer needed. 329 | 330 | - Request. This is the function name as an atom that will be called to process the request. 331 | 332 | - FullRequest. A tuple, containing internal request data: 333 | {ProcessingStrategy,Request,Arguments} 334 | ProcessingStrategy can be several atoms, it tells whether the incoming request is processed via a call, post or cast and also it tells whether the processing is done in parallel or serialized. 335 | Request is the same as right above. 336 | Arguments are the parameters the callback function for processing the request will get. 337 | 338 | - Ref. Unique reference of the request. Use this to identify the request in the start, stop and clean stage. 339 | 340 | - Params. Parameters of a statistic type. It is a property list that can be modified in runtime. 341 | 342 | - Tab. An ets table. Here we can save general statistical information that later can be retrieved by other services. The suggested practice of using this table is that the key shall be a tuple with two atoms, like {stat_type,request} and the value can be stats specific. Save the stats results here. 343 | 344 | - TempTab. An ets table to store temporary or permanent (as long as the service is running) metric data. The statistic function shall clean this up in case it had inserted something at start stage. The key shall be in the form of {stat_type_valuename,Request} for permanent and {stat_type_temporaryvalue,Ref} for tempoprary data like response time metric. Save your data needed for calculations here. 345 | 346 | - Timestamp. The time stamp in usecs when the request processing has finished. So you do not need to call erlang:now() for every stats to calculate values. 347 | 348 | ### Example 349 | In case of the already implemented response_time metric, there is a corresponding function in csi_stats.erl. When it is called with Stage = start, it saves the os:timestamp() value into the ets table with the following call: 350 | 351 | response_time(start, _Request, _R, Ref, _Params, _Tab, TempTab, TimeStamp) -> 352 | ets:insert(TempTab, {{response_time_first, Ref}, TimeStamp}); 353 | 354 | Next time the function is called shall be with Stage = stop. The response_time function, reads the above key from the ets table, makes some calculations and writes the statistics back to the table: 355 | 356 | ets:insert(Tab, {{response_time,Request},{NrOfRequests, 357 | CumulatedResponseTime, 358 | CumulatedResponseTime/NrOfRequests, 359 | MinRT, 360 | MaxRT}}) 361 | 362 | in the last step it deletes the {response_time_first,Ref} key from the ets table. 363 | 364 | In case it is called with Stage = clean instead, it just simply deletes the key that was inserted at start, from the ets table: 365 | 366 | ets:delete(Tab, {response_time_first,Ref}) 367 | 368 | And this goes on. There are numerous possibilities to turn statistics for a given service, function or stats_type on and off, and this can be done at runtime. Take a look at csi_server.erl for more details. 369 | 370 | For example: 371 | 372 | (em_server@127.0.0.1)14> csi:stats_get_all(em_service). 373 | [{{response_time,process_foo},{3,243,81.0,78,85}}] 374 | (em_server@127.0.0.1)15> 375 | 376 | meaning there were 3 requests for process_foo, the total time processing these requests was 243 usecs, The average time was 81 usecs, fastest request processing was 78 usecs, slowest was 85 usecs 377 | 378 | ## Generic application functionality 379 | 380 | In the stream above we saw how to implement a service using CSI. You might have recognized, there has been fixed part of the code especially when initializing a service. All calls for start, start_link, stop are the same for any service, and usually supervising the application is the same boring process for any app. 381 | 382 | To ease our life, there is an additional functionality of CSI. In case we use it, we can have CSI supervising our service servers by simply add the necessary information into our sys.config file. For example if we have a service called app_service, the service functions are in app_service.erl we can instruct CSI at startup to launch our server by including the following in sys.config: 383 | 384 | {csi,[{servers,[{app_service,app_service,[],default}] 385 | } 386 | ]} 387 | 388 | The servers tag in the tuple instruct CSI at startup to run through the list of tuples containing the information to launch a service. The format is {ServiceName, ServiceModule, InitArgs, ChildSpec}. The first three speak for themselves, the ChildSpec is the specification for the supervisor, how the service shall be added as a child. See supervisor:add_child/2 in Erlang docs. 389 | 390 | When we have this, there are three files needs to be maintained. 391 | 392 | The app_service.erl will be the same as above, but the app.erl will be a bit simpler. This is the API for the service and now purely contains the functionality: 393 | 394 | ```erlang 395 | -module(app). 396 | 397 | -define(SERVICE_NAME, app_service). 398 | -define(SERVICE_MODULE, app_service). 399 | 400 | %% ==================================================================== 401 | %% API functions 402 | %% ==================================================================== 403 | 404 | -export([process_foo/1, 405 | process_too_long/1, 406 | process_crashing/1]). 407 | 408 | process_foo(Atom) -> csi:call_p(?SERVICE_NAME, process_foo, [Atom]). 409 | process_too_long(Atom) -> csi:call_p(?SERVICE_NAME, process_too_long, [Atom]). 410 | process_crashing(Atom) -> csi:call_p(?SERVICE_NAME, process_crashing, [Atom]). 411 | ``` 412 | 413 | The other two files, relx.config and app.src remains the same as above. 414 | 415 | An example is set up in the generic_app directory, so in case you would like to start developing your service, you might copy the directory, make the changes for your service and start extending it with the functionality you need. 416 | 417 | ### sys.config Child Specification 418 | 419 | In case you need other than defaul child specification, here is an example for your sys.config: 420 | 421 | {csi,[{servers,[{app_service,app_service,[], 422 | #{id => app_service, 423 | start => {csi, 424 | start_link, 425 | [app_service, app_service, []]}, 426 | restart => permanent, 427 | shutdown => 2000, 428 | type => worker, 429 | modules => [app,app_service]}}] 430 | }] 431 | } 432 | 433 | So to use a ChildSpec as a map, instead of just using the default values that are the same as in the example here. 434 | 435 | Feel free to come up with more lightweight stats! 436 | 437 | ## Please share your thoughts, suggest improvements, find bugs and report them! 438 | 439 | -------------------------------------------------------------------------------- /config/generate-config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- erlang -*- 3 | %%! -s inets start -name beamup@127.0.0.1 4 | 5 | %% Usage: 6 | %% generate-config.sh --deps dep1 dep2 ... --build-env dev 7 | %% 8 | %% This script has two main functions. At first it makes possible to generate 9 | %% sys.config files from template.sys.config file by replacing the variable 10 | %% references from config.vars file, according to the build environment specified. 11 | %% 12 | %% It also does that in the specified dependencies and merge the sections from 13 | %% the dependencies to the main sys.config. 14 | %% 15 | %% Example: 16 | %% 17 | %% deps/csi/config/sys.config 18 | %% deps/ws/config/template.sys.config + config.vars 19 | %% config/sys.config 20 | %% config/override.vars (which has a special format like this) 21 | %% 22 | %% {dev, [ 23 | %% {"lager.log_root", "mystuff/log"} 24 | %% ]}. 25 | %% {stage, [ 26 | %% %% This will be replaced 27 | %% {"lager.crash_log_count", 10}, 28 | %% %% This will be removed 29 | %% "lager.error_logger_hwm" 30 | %% ]}. 31 | %% 32 | %% * get the csi config section from deps/csi/config/sys.config 33 | %% * replace the variables in deps/ws/config/template.sys.config and generate 34 | %% a sys.config there 35 | %% * get the ws secion from deps/ws/config/sys.config 36 | %% * iterate over all sections in config/sys.config and 37 | %% * if there is no csi section, it copies the csi section of the 1st step 38 | %% * if there is such section, it keeps that 39 | %% ... 40 | %% 41 | %% In that way we can collect the config of the dependencies but also we have 42 | %% possibility to override them one by one. 43 | %% * Apply override.vars which contains rules for build envs. Like 44 | %% {"csi.timeout", 50} overrides the timeout key in csi config given that 45 | %% {csi, [ 46 | %% ... 47 | %% {timeout, 10} 48 | %% ]} 49 | %% is the config. If there is no timeout key, it will add. 50 | %% We also can remove config value to specify only the key without value 51 | %% "csi.debug" 52 | 53 | main(Args) -> 54 | Opts = parse_opts(Args), 55 | %% --deps are optional but if we don't have --build-env set, let us fail 56 | case lists:keyfind(env, 1, Opts) of 57 | false -> 58 | io:format("Usage: generate-config.sh --deps dep1 dep2 --build-env local|dev|...~n"), 59 | init:halt(1); 60 | _ -> 61 | ok 62 | end, 63 | 64 | {env, Env} = lists:keyfind(env, 1, Opts), 65 | Deps = get_deps(Opts), 66 | generate_deps(Deps, Env), 67 | generate_sys_config(".", Env), 68 | DepConfigs = lists:flatten([extract_dep_config(Dep) || Dep <- Deps]), 69 | Configs = apply_main_sys_config(DepConfigs), 70 | dump_configs("config/sys.config", Configs). 71 | 72 | parse_opts(Opts) -> 73 | parse_opts(Opts, []). 74 | 75 | parse_opts([], Result) -> 76 | Result; 77 | parse_opts(["--deps" | Rest], Result) -> 78 | {Vals, Rest2} = take_values(Rest, []), 79 | parse_opts(Rest2, [{deps, Vals} | Result]); 80 | parse_opts(["--build-env", Env | Rest], Result) -> 81 | parse_opts(Rest, [{env, Env} | Result]); 82 | parse_opts(["--build-env"], Result) -> 83 | io:format("Default build-env=dev~n",[]), 84 | parse_opts([],[{env, "dev"} | Result]); 85 | parse_opts(Opts, _Result) -> 86 | err("Cannot parse ~p~n", [Opts]). 87 | 88 | take_values([], Vs) -> 89 | {Vs, []}; 90 | take_values([[$-, $- | _] | _] = Rest, Vs) -> 91 | {Vs, Rest}; 92 | take_values([V | Rest], Vs) -> 93 | take_values(Rest, Vs ++ [V]). 94 | 95 | get_deps(Opts) -> 96 | case lists:keyfind(deps, 1, Opts) of 97 | false -> 98 | []; 99 | {deps, Deps} -> 100 | Deps 101 | end. 102 | 103 | generate_sys_config(Dir, EnvName) -> 104 | %% Generate sys.config from template.sys.config depending on the env 105 | TemplateSysConfig = filename:join(Dir, "config/template.sys.config"), 106 | case filelib:is_file(TemplateSysConfig) of 107 | true -> 108 | process_template(Dir, EnvName); 109 | false -> 110 | ok 111 | end, 112 | %% Apply override vars if there is such a file, and there is sys.config 113 | SysConfig = filename:join(Dir, "config/sys.config"), 114 | Override = filename:join(Dir, "config/override.vars"), 115 | case filelib:is_file(SysConfig) andalso filelib:is_file(Override) of 116 | true -> 117 | apply_override(SysConfig, Override, EnvName); 118 | false -> 119 | ok 120 | end. 121 | 122 | %% Generate sys.config from template.sys.config + config.vars 123 | %% If template is missing, it left sys.config as it is (no templating there) 124 | %% If config.vars is missing, it copies template to sys.config 125 | process_template(Dir, EnvName) -> 126 | VarsFile = filename:join(Dir, "config/config.vars"), 127 | Template = filename:join(Dir, "config/template.sys.config"), 128 | SysConf = filename:join(Dir, "config/sys.config"), 129 | Vars = read_vars(VarsFile, EnvName), 130 | case file:read_file(Template) of 131 | {ok, File} -> 132 | case re:run(File, <<"\\${(.*?)}">>, 133 | [{capture, all_but_first, binary}, global]) of 134 | nomatch -> 135 | file:copy(Template, SysConf); 136 | {match, Vs} -> 137 | %% Get the ${var} variable names 138 | Vss = [binary_to_atom(V, utf8) || [V] <- Vs], 139 | %% Replace them 140 | Out = lists:foldl( 141 | fun(Var, Acc) -> 142 | case lists:keyfind(Var, 1, Vars) of 143 | {Var, Value} -> 144 | LV = case Value of 145 | _ when is_integer(Value) -> 146 | integer_to_list(Value); 147 | _ when is_atom(Value) -> 148 | atom_to_list(Value); 149 | _ -> 150 | "\"" ++ Value ++ "\"" 151 | end, 152 | Re = "\\${" ++ atom_to_list(Var) ++ "}", 153 | re:replace(Acc, Re, LV, [global]); 154 | false -> 155 | err("No value for ~p~n", [Var]) 156 | end 157 | end, File, Vss), 158 | %% Write the output 159 | file:write_file(SysConf, Out) 160 | end; 161 | {error, enoent} -> 162 | %% No template, leave sys.config as it is 163 | ok 164 | end. 165 | 166 | apply_override(SysConfig, Override, EnvName) -> 167 | Env = list_to_atom(EnvName), 168 | {ok, [Sys]} = file:consult(SysConfig), 169 | {ok, Over} = file:consult(Override), 170 | case lists:keyfind(Env, 1, Over) of 171 | false -> 172 | ok; 173 | {_, Vars} -> 174 | Sys2 = lists:foldl( 175 | fun(Var, Acc) -> 176 | apply_path(Acc, Var) 177 | end, Sys, Vars), 178 | dump_configs(SysConfig, Sys2) 179 | end. 180 | 181 | atom_tokens(String) -> 182 | [list_to_atom(T) || T <- string:tokens(String, ".")]. 183 | 184 | %% Deep delete/replace a "key1.key2.key3" path in a term 185 | apply_path(Term, {KeyPath, Value}) -> 186 | apply_path2(Term, atom_tokens(KeyPath), store, Value); 187 | apply_path(Term, KeyPath) -> 188 | apply_path2(Term, atom_tokens(KeyPath), del, undefined). 189 | 190 | %% Deep delete/replace a key list in a term 191 | apply_path2(Term , [], _Op, _Value) -> 192 | Term; 193 | apply_path2(Term, [Key], store, Value) -> 194 | lists:keystore(Key, 1, Term, {Key, Value}); 195 | apply_path2(Term, [Key], del, _Value) -> 196 | lists:keydelete(Key, 1, Term); 197 | apply_path2(Term, [Key | Rest], Op, Value) -> 198 | SubTerm = case lists:keyfind(Key, 1, Term) of 199 | false -> 200 | []; 201 | {Key, V} -> 202 | V 203 | end, 204 | R = apply_path2(SubTerm, Rest, Op, Value), 205 | lists:keystore(Key, 1, Term, {Key, R}). 206 | 207 | extract_dep_config(Dep) -> 208 | case file:consult(filename:join(["deps", Dep, "config/sys.config"])) of 209 | {ok, [Term]} -> 210 | Key = list_to_atom(Dep), 211 | lists:filter( 212 | fun({K, _}) when K =:= Key -> true; 213 | (_) -> false 214 | end, Term); 215 | _ -> 216 | [] 217 | end. 218 | 219 | apply_main_sys_config(DepConfigs) -> 220 | {ok, [Main]} = file:consult("config/sys.config"), 221 | %% Override the keys in DepConfigs with sys.config 222 | Result = lists:foldl( 223 | fun({Key, Value}, Acc) -> 224 | case lists:keyfind(Key, 1, Main) of 225 | false -> 226 | %% New key, wasn't in the main sys.config 227 | %% Let us add it to that 228 | [{Key, Value} | Acc]; 229 | _ -> 230 | %% Main sys.config overrides this key, 231 | %% so do nothing here 232 | Acc 233 | end 234 | end, Main, DepConfigs), 235 | lists:keysort(1, Result). 236 | 237 | dump_configs(Filename, Configs) -> 238 | {ok, Dev} = file:open(Filename, [write]), 239 | io:format(Dev, "~p.", [Configs]), 240 | file:close(Dev). 241 | 242 | generate_deps(Deps, Env) -> 243 | [generate_sys_config("deps/" ++ Dep, Env) || Dep <- Deps]. 244 | 245 | read_vars(VarsFile, EnvName) -> 246 | case file:consult(VarsFile) of 247 | {ok, AllVars} -> 248 | Env = list_to_atom(EnvName), 249 | case lists:keyfind(Env, 1, AllVars) of 250 | {Env, Vars} -> 251 | Vars; 252 | _ -> 253 | io:format("Could not find vars for env:~p~n", [EnvName]), 254 | [] 255 | end; 256 | {error, enoent} -> 257 | []; 258 | ELSE -> 259 | io:format("heyhey:~p~n", [ELSE]), 260 | [] 261 | end. 262 | 263 | err(Msg, Args) -> 264 | io:format(Msg, Args), 265 | init:halt(1). 266 | 267 | -------------------------------------------------------------------------------- /config/sys.config: -------------------------------------------------------------------------------- 1 | []. -------------------------------------------------------------------------------- /config/vm.args: -------------------------------------------------------------------------------- 1 | ## Name of the node 2 | -name csi_server 3 | 4 | ## Cookie for distributed erlang 5 | -setcookie csi_server 6 | 7 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 8 | ## (Disabled by default..use with caution!) 9 | ##-heart 10 | 11 | ## Enable kernel poll and a few async threads 12 | ##+K true 13 | ##+A 5 14 | 15 | ## Increase number of concurrent ports/sockets 16 | ##-env ERL_MAX_PORTS 4096 17 | 18 | ## Tweak GC to run more often 19 | ##-env ERL_FULLSWEEP_AFTER 10 20 | -hidden 21 | -connect_all false 22 | -------------------------------------------------------------------------------- /elvis.config: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | elvis, 4 | [ 5 | {config, 6 | [#{dirs => ["src"], 7 | filter => "*.erl", 8 | rules => [{elvis_style, line_length, #{limit => 80, 9 | skip_comments => false}}, 10 | {elvis_style, no_tabs}, 11 | {elvis_style, no_trailing_whitespace}, 12 | {elvis_style, macro_names}, 13 | {elvis_style, macro_module_names}, 14 | {elvis_style, operator_spaces, #{rules => [{right, ","}, 15 | {right, "++"}, 16 | {left, "++"}]}}, 17 | {elvis_style, nesting_level, #{level => 3}}, 18 | {elvis_style, god_modules, #{limit => 25}}, 19 | {elvis_style, no_if_expression}, 20 | {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, 21 | {elvis_style, used_ignored_variable}, 22 | {elvis_style, no_behavior_info}, 23 | { 24 | elvis_style, 25 | module_naming_convention, 26 | #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$", 27 | ignore => []} 28 | }, 29 | {elvis_style, state_record_and_type}, 30 | {elvis_style, no_spec_with_records}, 31 | {elvis_style, dont_repeat_yourself, #{min_complexity => 10}} 32 | ] 33 | }, 34 | #{dirs => ["."], 35 | filter => "Makefile", 36 | rules => [{elvis_project, no_deps_master_erlang_mk, #{ignore => []}}, 37 | {elvis_project, protocol_for_deps_erlang_mk, #{ignore => []}}] 38 | }, 39 | #{dirs => ["."], 40 | filter => "rebar.config", 41 | rules => [{elvis_project, no_deps_master_rebar, #{ignore => []}}, 42 | {elvis_project, protocol_for_deps_rebar, #{ignore => []}}] 43 | }, 44 | #{dirs => ["."], 45 | filter => "elvis.config", 46 | rules => [{elvis_project, old_configuration_format}] 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | ]. 53 | -------------------------------------------------------------------------------- /example/config/config.vars: -------------------------------------------------------------------------------- 1 | {local, [ 2 | {timer_sleep, 5000} 3 | ]}. 4 | 5 | {localdev, [ 6 | {timer_sleep, 5000} 7 | ]}. 8 | 9 | {dev, [ 10 | {timer_sleep, 5000} 11 | ]}. 12 | 13 | {stage, [ 14 | {timer_sleep, 5000} 15 | ]}. 16 | 17 | {prod, [ 18 | {timer_sleep, 5000} 19 | ]}. 20 | 21 | -------------------------------------------------------------------------------- /example/config/generate-config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- erlang -*- 3 | %%! -s inets start -name beamup@127.0.0.1 4 | 5 | %% Usage: 6 | %% generate-config.sh --deps dep1 dep2 ... --build-env dev 7 | %% 8 | %% This script has two main functions. At first it makes possible to generate 9 | %% sys.config files from template.sys.config file by replacing the variable 10 | %% references from config.vars file, according to the build environment specified. 11 | %% 12 | %% It also does that in the specified dependencies and merge the sections from 13 | %% the dependencies to the main sys.config. 14 | %% 15 | %% Example: 16 | %% 17 | %% deps/csi/config/sys.config 18 | %% deps/ws/config/template.sys.config + config.vars 19 | %% config/sys.config 20 | %% config/override.vars (which has a special format like this) 21 | %% 22 | %% {dev, [ 23 | %% {"lager.log_root", "mystuff/log"} 24 | %% ]}. 25 | %% {stage, [ 26 | %% %% This will be replaced 27 | %% {"lager.crash_log_count", 10}, 28 | %% %% This will be removed 29 | %% "lager.error_logger_hwm" 30 | %% ]}. 31 | %% 32 | %% * get the csi config section from deps/csi/config/sys.config 33 | %% * replace the variables in deps/ws/config/template.sys.config and generate 34 | %% a sys.config there 35 | %% * get the ws secion from deps/ws/config/sys.config 36 | %% * iterate over all sections in config/sys.config and 37 | %% * if there is no csi section, it copies the csi section of the 1st step 38 | %% * if there is such section, it keeps that 39 | %% ... 40 | %% 41 | %% In that way we can collect the config of the dependencies but also we have 42 | %% possibility to override them one by one. 43 | %% * Apply override.vars which contains rules for build envs. Like 44 | %% {"csi.timeout", 50} overrides the timeout key in csi config given that 45 | %% {csi, [ 46 | %% ... 47 | %% {timeout, 10} 48 | %% ]} 49 | %% is the config. If there is no timeout key, it will add. 50 | %% We also can remove config value to specify only the key without value 51 | %% "csi.debug" 52 | 53 | main(Args) -> 54 | Opts = parse_opts(Args), 55 | %% --deps are optional but if we don't have --build-env set, let us fail 56 | case lists:keyfind(env, 1, Opts) of 57 | false -> 58 | io:format("Usage: generate-config.sh --deps dep1 dep2 --build-env local|dev|...~n"), 59 | init:halt(1); 60 | _ -> 61 | ok 62 | end, 63 | 64 | {env, Env} = lists:keyfind(env, 1, Opts), 65 | Deps = get_deps(Opts), 66 | generate_deps(Deps, Env), 67 | generate_sys_config(".", Env), 68 | DepConfigs = lists:flatten([extract_dep_config(Dep) || Dep <- Deps]), 69 | Configs = apply_main_sys_config(DepConfigs), 70 | dump_configs("config/sys.config", Configs). 71 | 72 | parse_opts(Opts) -> 73 | parse_opts(Opts, []). 74 | 75 | parse_opts([], Result) -> 76 | Result; 77 | parse_opts(["--deps" | Rest], Result) -> 78 | {Vals, Rest2} = take_values(Rest, []), 79 | parse_opts(Rest2, [{deps, Vals} | Result]); 80 | parse_opts(["--build-env", Env | Rest], Result) -> 81 | parse_opts(Rest, [{env, Env} | Result]); 82 | parse_opts(["--build-env"], Result) -> 83 | io:format("Default build-env=dev~n",[]), 84 | parse_opts([],[{env, "dev"} | Result]); 85 | parse_opts(Opts, _Result) -> 86 | err("Cannot parse ~p~n", [Opts]). 87 | 88 | take_values([], Vs) -> 89 | {Vs, []}; 90 | take_values([[$-, $- | _] | _] = Rest, Vs) -> 91 | {Vs, Rest}; 92 | take_values([V | Rest], Vs) -> 93 | take_values(Rest, Vs ++ [V]). 94 | 95 | get_deps(Opts) -> 96 | case lists:keyfind(deps, 1, Opts) of 97 | false -> 98 | []; 99 | {deps, Deps} -> 100 | Deps 101 | end. 102 | 103 | generate_sys_config(Dir, EnvName) -> 104 | %% Generate sys.config from template.sys.config depending on the env 105 | TemplateSysConfig = filename:join(Dir, "config/template.sys.config"), 106 | case filelib:is_file(TemplateSysConfig) of 107 | true -> 108 | process_template(Dir, EnvName); 109 | false -> 110 | ok 111 | end, 112 | %% Apply override vars if there is such a file, and there is sys.config 113 | SysConfig = filename:join(Dir, "config/sys.config"), 114 | Override = filename:join(Dir, "config/override.vars"), 115 | case filelib:is_file(SysConfig) andalso filelib:is_file(Override) of 116 | true -> 117 | apply_override(SysConfig, Override, EnvName); 118 | false -> 119 | ok 120 | end. 121 | 122 | %% Generate sys.config from template.sys.config + config.vars 123 | %% If template is missing, it left sys.config as it is (no templating there) 124 | %% If config.vars is missing, it copies template to sys.config 125 | process_template(Dir, EnvName) -> 126 | VarsFile = filename:join(Dir, "config/config.vars"), 127 | Template = filename:join(Dir, "config/template.sys.config"), 128 | SysConf = filename:join(Dir, "config/sys.config"), 129 | Vars = read_vars(VarsFile, EnvName), 130 | case file:read_file(Template) of 131 | {ok, File} -> 132 | case re:run(File, <<"\\${(.*?)}">>, 133 | [{capture, all_but_first, binary}, global]) of 134 | nomatch -> 135 | file:copy(Template, SysConf); 136 | {match, Vs} -> 137 | %% Get the ${var} variable names 138 | Vss = [binary_to_atom(V, utf8) || [V] <- Vs], 139 | %% Replace them 140 | Out = lists:foldl( 141 | fun(Var, Acc) -> 142 | case lists:keyfind(Var, 1, Vars) of 143 | {Var, Value} -> 144 | LV = case Value of 145 | _ when is_integer(Value) -> 146 | integer_to_list(Value); 147 | _ when is_atom(Value) -> 148 | atom_to_list(Value); 149 | _ -> 150 | "\"" ++ Value ++ "\"" 151 | end, 152 | Re = "\\${" ++ atom_to_list(Var) ++ "}", 153 | re:replace(Acc, Re, LV, [global]); 154 | false -> 155 | err("No value for ~p~n", [Var]) 156 | end 157 | end, File, Vss), 158 | %% Write the output 159 | file:write_file(SysConf, Out) 160 | end; 161 | {error, enoent} -> 162 | %% No template, leave sys.config as it is 163 | ok 164 | end. 165 | 166 | apply_override(SysConfig, Override, EnvName) -> 167 | Env = list_to_atom(EnvName), 168 | {ok, [Sys]} = file:consult(SysConfig), 169 | {ok, Over} = file:consult(Override), 170 | case lists:keyfind(Env, 1, Over) of 171 | false -> 172 | ok; 173 | {_, Vars} -> 174 | Sys2 = lists:foldl( 175 | fun(Var, Acc) -> 176 | apply_path(Acc, Var) 177 | end, Sys, Vars), 178 | dump_configs(SysConfig, Sys2) 179 | end. 180 | 181 | atom_tokens(String) -> 182 | [list_to_atom(T) || T <- string:tokens(String, ".")]. 183 | 184 | %% Deep delete/replace a "key1.key2.key3" path in a term 185 | apply_path(Term, {KeyPath, Value}) -> 186 | apply_path2(Term, atom_tokens(KeyPath), store, Value); 187 | apply_path(Term, KeyPath) -> 188 | apply_path2(Term, atom_tokens(KeyPath), del, undefined). 189 | 190 | %% Deep delete/replace a key list in a term 191 | apply_path2(Term , [], _Op, _Value) -> 192 | Term; 193 | apply_path2(Term, [Key], store, Value) -> 194 | lists:keystore(Key, 1, Term, {Key, Value}); 195 | apply_path2(Term, [Key], del, _Value) -> 196 | lists:keydelete(Key, 1, Term); 197 | apply_path2(Term, [Key | Rest], Op, Value) -> 198 | SubTerm = case lists:keyfind(Key, 1, Term) of 199 | false -> 200 | []; 201 | {Key, V} -> 202 | V 203 | end, 204 | R = apply_path2(SubTerm, Rest, Op, Value), 205 | lists:keystore(Key, 1, Term, {Key, R}). 206 | 207 | extract_dep_config(Dep) -> 208 | case file:consult(filename:join(["deps", Dep, "config/sys.config"])) of 209 | {ok, [Term]} -> 210 | Key = list_to_atom(Dep), 211 | lists:filter( 212 | fun({K, _}) when K =:= Key -> true; 213 | (_) -> false 214 | end, Term); 215 | _ -> 216 | [] 217 | end. 218 | 219 | apply_main_sys_config(DepConfigs) -> 220 | {ok, [Main]} = file:consult("config/sys.config"), 221 | %% Override the keys in DepConfigs with sys.config 222 | Result = lists:foldl( 223 | fun({Key, Value}, Acc) -> 224 | case lists:keyfind(Key, 1, Main) of 225 | false -> 226 | %% New key, wasn't in the main sys.config 227 | %% Let us add it to that 228 | [{Key, Value} | Acc]; 229 | _ -> 230 | %% Main sys.config overrides this key, 231 | %% so do nothing here 232 | Acc 233 | end 234 | end, Main, DepConfigs), 235 | lists:keysort(1, Result). 236 | 237 | dump_configs(Filename, Configs) -> 238 | {ok, Dev} = file:open(Filename, [write]), 239 | io:format(Dev, "~p.", [Configs]), 240 | file:close(Dev). 241 | 242 | generate_deps(Deps, Env) -> 243 | [generate_sys_config("deps/" ++ Dep, Env) || Dep <- Deps]. 244 | 245 | read_vars(VarsFile, EnvName) -> 246 | case file:consult(VarsFile) of 247 | {ok, AllVars} -> 248 | Env = list_to_atom(EnvName), 249 | case lists:keyfind(Env, 1, AllVars) of 250 | {Env, Vars} -> 251 | Vars; 252 | _ -> 253 | io:format("Could not find vars for env:~p~n", [EnvName]), 254 | [] 255 | end; 256 | {error, enoent} -> 257 | []; 258 | ELSE -> 259 | io:format("heyhey:~p~n", [ELSE]), 260 | [] 261 | end. 262 | 263 | err(Msg, Args) -> 264 | io:format(Msg, Args), 265 | init:halt(1). 266 | 267 | -------------------------------------------------------------------------------- /example/config/template.sys.config: -------------------------------------------------------------------------------- 1 | %% 2 | %% With the template sys.config we can define a generic sys.config which 3 | %% will be processed later by the generate-config.sh script. 4 | %% During the processing the variable references will be substituted with 5 | %% the actual content of the variables depending on the build environment. 6 | [{em, [ 7 | {timer_sleep, ${timer_sleep}} 8 | ]} 9 | ]. 10 | -------------------------------------------------------------------------------- /example/config/vm.args: -------------------------------------------------------------------------------- 1 | 2 | -name em@127.0.0.1 3 | -setcookie em_release 4 | 5 | -------------------------------------------------------------------------------- /example/elvis.config: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | elvis, 4 | [ 5 | {config, 6 | [#{dirs => ["src"], 7 | filter => "*.erl", 8 | rules => [{elvis_style, line_length, #{limit => 80, 9 | skip_comments => false}}, 10 | {elvis_style, no_tabs}, 11 | {elvis_style, no_trailing_whitespace}, 12 | {elvis_style, macro_names}, 13 | {elvis_style, macro_module_names}, 14 | {elvis_style, operator_spaces, #{rules => [{right, ","}, 15 | {right, "++"}, 16 | {left, "++"}]}}, 17 | {elvis_style, nesting_level, #{level => 3}}, 18 | {elvis_style, god_modules, #{limit => 25}}, 19 | {elvis_style, no_if_expression}, 20 | {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, 21 | {elvis_style, used_ignored_variable}, 22 | {elvis_style, no_behavior_info}, 23 | { 24 | elvis_style, 25 | module_naming_convention, 26 | #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$", 27 | ignore => []} 28 | }, 29 | {elvis_style, state_record_and_type}, 30 | {elvis_style, no_spec_with_records}, 31 | {elvis_style, dont_repeat_yourself, #{min_complexity => 10}} 32 | ] 33 | }, 34 | #{dirs => ["."], 35 | filter => "Makefile", 36 | rules => [{elvis_project, no_deps_master_erlang_mk, #{ignore => []}}, 37 | {elvis_project, protocol_for_deps_erlang_mk, #{ignore => []}}] 38 | }, 39 | #{dirs => ["."], 40 | filter => "rebar.config", 41 | rules => [{elvis_project, no_deps_master_rebar, #{ignore => []}}, 42 | {elvis_project, protocol_for_deps_rebar, #{ignore => []}}] 43 | }, 44 | #{dirs => ["."], 45 | filter => "elvis.config", 46 | rules => [{elvis_project, old_configuration_format}] 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | ]. 53 | -------------------------------------------------------------------------------- /example/rebar.config: -------------------------------------------------------------------------------- 1 | {deps_dir, "deps"}. 2 | 3 | {erl_opts, [debug_info, 4 | {parse_transform, lager_transform}, 5 | {define, lager} 6 | ]}. 7 | {deps, [ 8 | {lager, {git, "git@github.com:basho/lager.git", {tag, "3.0.2"}}}, 9 | {csi, {git, "git@github.com:esl/CommonServiceInterface.git", {branch, "master"}}} 10 | ]}. 11 | 12 | {pre_hooks, [{compile, "config/generate-config.sh --deps ${BUILD_DEPS} --build-env ${BUILD_ENV}"}]}. 13 | 14 | {relx, [{release, {csi_example, "0.0.1"}, 15 | [runtime_tools, 16 | csi 17 | ]}, 18 | {dev_mode, false}, 19 | {include_erts, true}, 20 | {sys_config, "config/sys.config"}, 21 | {vm_args, "config/vm.args"}, 22 | {extended_start_script, true}]}. 23 | 24 | {dialyzer, [ 25 | {warnings, [underspecs, no_return]}, 26 | {get_warnings, true}, 27 | {plt_apps, top_level_deps}, % top_level_deps | all_deps 28 | {plt_extra_apps, [lager]}, 29 | {plt_location, local}, % local | "/my/file/name" 30 | {plt_prefix, "rebar3"}, 31 | {base_plt_apps, [stdlib, kernel]}, 32 | {base_plt_location, global}, % global | "/my/file/name" 33 | {base_plt_prefix, "rebar3"} 34 | ]}. 35 | 36 | -------------------------------------------------------------------------------- /example/rebar.config.script: -------------------------------------------------------------------------------- 1 | 2 | %% In dev, release dependencies will be symlinked in the _rel//lib 3 | Env = os:getenv("BUILD_ENV"), 4 | %io:format("Build env is ~p~n", [Env]), 5 | %io:format("CONFIG is:~p~n", [CONFIG]), 6 | 7 | Symlink = case Env of 8 | false -> 9 | true; 10 | "dev" -> 11 | true; 12 | "local" -> 13 | true; 14 | _ -> 15 | false 16 | end, 17 | 18 | case Symlink of 19 | true -> 20 | % io:format("relx dev_mode enabled, deps are symlinked~n", []), 21 | [{dev_mode, true} | CONFIG]; 22 | false -> 23 | CONFIG 24 | end. 25 | 26 | -------------------------------------------------------------------------------- /example/relx.config: -------------------------------------------------------------------------------- 1 | {release, {em_server, "0.0.1"}, 2 | [csi, 3 | em, 4 | runtime_tools]}. 5 | {sys_config,"config/sys.config"}. 6 | {vm_args,"config/vm.args"}. 7 | {extended_start_script, true}. 8 | -------------------------------------------------------------------------------- /example/src/em.app.src: -------------------------------------------------------------------------------- 1 | {application, em, [ 2 | {description, "EM service"}, 3 | {vsn, "0.0.1"}, 4 | {id, "git"}, 5 | {modules, []}, 6 | {registered, []}, 7 | {applications, [ 8 | csi, 9 | compiler, 10 | kernel, 11 | stdlib, 12 | goldrush, 13 | lager 14 | ]}, 15 | {env, []} 16 | ]}. 17 | -------------------------------------------------------------------------------- /example/src/em.erl: -------------------------------------------------------------------------------- 1 | -module(em). 2 | 3 | -define(SERVICE_NAME, em_service). 4 | -define(SERVICE_MODULE, em_service). 5 | 6 | %% ==================================================================== 7 | %% API functions 8 | %% ==================================================================== 9 | -export([start/0, 10 | start_link/0, 11 | stop/0]). 12 | 13 | -export([process_foo/1, 14 | process_too_long/1, 15 | process_crashing/1]). 16 | 17 | 18 | start() -> csi:start(?SERVICE_NAME, ?SERVICE_MODULE). 19 | start_link() -> csi:start_link(?SERVICE_NAME, ?SERVICE_MODULE). 20 | 21 | stop() -> csi:stop(?SERVICE_NAME). 22 | 23 | process_foo(Atom) -> csi:call_p(?SERVICE_NAME, process_foo, [Atom]). 24 | process_too_long(Atom) -> csi:call_p(?SERVICE_NAME, process_too_long, [Atom]). 25 | process_crashing(Atom) -> csi:call_p(?SERVICE_NAME, process_crashing, [Atom]). 26 | -------------------------------------------------------------------------------- /example/src/em_service.erl: -------------------------------------------------------------------------------- 1 | -module(em_service). 2 | -behaviour(csi_server). 3 | 4 | %% General state of the service 5 | -record(em_state, {}). 6 | 7 | %% Lifecycle State for every requests' 8 | -record(em_session_state, {}). 9 | 10 | -export([init_service/1, 11 | init/2, 12 | terminate/2, 13 | terminate_service/2]). 14 | 15 | -export([process_foo/2, 16 | process_too_long/2, 17 | process_crashing/2]). 18 | 19 | 20 | %% ==================================================================== 21 | %% Behavioural functions 22 | %% ==================================================================== 23 | init_service(_InitArgs) -> 24 | {ok, #em_state{}}. 25 | 26 | init(_Args, _ServiceState) -> 27 | {ok, #em_session_state{}}. 28 | 29 | terminate(_Reason, _State) -> 30 | ok. 31 | 32 | terminate_service(_Reason, _State) -> 33 | ok. 34 | 35 | %% ==================================================================== 36 | %% Service functions 37 | %% ==================================================================== 38 | process_foo(_Args, State) -> 39 | {hello_world, State}. 40 | 41 | process_too_long(_Args, State) -> 42 | {ok, Sleep} = application:get_env(em, timer_sleep), 43 | timer:sleep(Sleep), 44 | {long_job_finished, State}. 45 | 46 | process_crashing(Args, State) -> 47 | A = Args - Args, 48 | {A, State}. 49 | -------------------------------------------------------------------------------- /generic_app/config/.gitignore: -------------------------------------------------------------------------------- 1 | /sys.config 2 | -------------------------------------------------------------------------------- /generic_app/config/config.vars: -------------------------------------------------------------------------------- 1 | {local, [ 2 | {timer_sleep, 5000} 3 | ]}. 4 | 5 | {localdev, [ 6 | {timer_sleep, 5000} 7 | ]}. 8 | 9 | {dev, [ 10 | {timer_sleep, 5000} 11 | ]}. 12 | 13 | {stage, [ 14 | {timer_sleep, 5000} 15 | ]}. 16 | 17 | {prod, [ 18 | {timer_sleep, 5000} 19 | ]}. 20 | 21 | -------------------------------------------------------------------------------- /generic_app/config/generate-config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- erlang -*- 3 | %%! -s inets start -name beamup@127.0.0.1 4 | 5 | %% Usage: 6 | %% generate-config.sh --deps dep1 dep2 ... --build-env dev 7 | %% 8 | %% This script has two main functions. At first it makes possible to generate 9 | %% sys.config files from template.sys.config file by replacing the variable 10 | %% references from config.vars file, according to the build environment specified. 11 | %% 12 | %% It also does that in the specified dependencies and merge the sections from 13 | %% the dependencies to the main sys.config. 14 | %% 15 | %% Example: 16 | %% 17 | %% deps/csi/config/sys.config 18 | %% deps/ws/config/template.sys.config + config.vars 19 | %% config/sys.config 20 | %% config/override.vars (which has a special format like this) 21 | %% 22 | %% {dev, [ 23 | %% {"lager.log_root", "mystuff/log"} 24 | %% ]}. 25 | %% {stage, [ 26 | %% %% This will be replaced 27 | %% {"lager.crash_log_count", 10}, 28 | %% %% This will be removed 29 | %% "lager.error_logger_hwm" 30 | %% ]}. 31 | %% 32 | %% * get the csi config section from deps/csi/config/sys.config 33 | %% * replace the variables in deps/ws/config/template.sys.config and generate 34 | %% a sys.config there 35 | %% * get the ws secion from deps/ws/config/sys.config 36 | %% * iterate over all sections in config/sys.config and 37 | %% * if there is no csi section, it copies the csi section of the 1st step 38 | %% * if there is such section, it keeps that 39 | %% ... 40 | %% 41 | %% In that way we can collect the config of the dependencies but also we have 42 | %% possibility to override them one by one. 43 | %% * Apply override.vars which contains rules for build envs. Like 44 | %% {"csi.timeout", 50} overrides the timeout key in csi config given that 45 | %% {csi, [ 46 | %% ... 47 | %% {timeout, 10} 48 | %% ]} 49 | %% is the config. If there is no timeout key, it will add. 50 | %% We also can remove config value to specify only the key without value 51 | %% "csi.debug" 52 | 53 | main(Args) -> 54 | Opts = parse_opts(Args), 55 | %% --deps are optional but if we don't have --build-env set, let us fail 56 | case lists:keyfind(env, 1, Opts) of 57 | false -> 58 | io:format("Usage: generate-config.sh --deps dep1 dep2 --build-env local|dev|...~n"), 59 | init:halt(1); 60 | _ -> 61 | ok 62 | end, 63 | 64 | {env, Env} = lists:keyfind(env, 1, Opts), 65 | Deps = get_deps(Opts), 66 | generate_deps(Deps, Env), 67 | generate_sys_config(".", Env), 68 | DepConfigs = lists:flatten([extract_dep_config(Dep) || Dep <- Deps]), 69 | Configs = apply_main_sys_config(DepConfigs), 70 | dump_configs("config/sys.config", Configs). 71 | 72 | parse_opts(Opts) -> 73 | parse_opts(Opts, []). 74 | 75 | parse_opts([], Result) -> 76 | Result; 77 | parse_opts(["--deps" | Rest], Result) -> 78 | {Vals, Rest2} = take_values(Rest, []), 79 | parse_opts(Rest2, [{deps, Vals} | Result]); 80 | parse_opts(["--build-env", Env | Rest], Result) -> 81 | parse_opts(Rest, [{env, Env} | Result]); 82 | parse_opts(["--build-env"], Result) -> 83 | io:format("Default build-env=dev~n",[]), 84 | parse_opts([],[{env, "dev"} | Result]); 85 | parse_opts(Opts, _Result) -> 86 | err("Cannot parse ~p~n", [Opts]). 87 | 88 | take_values([], Vs) -> 89 | {Vs, []}; 90 | take_values([[$-, $- | _] | _] = Rest, Vs) -> 91 | {Vs, Rest}; 92 | take_values([V | Rest], Vs) -> 93 | take_values(Rest, Vs ++ [V]). 94 | 95 | get_deps(Opts) -> 96 | case lists:keyfind(deps, 1, Opts) of 97 | false -> 98 | []; 99 | {deps, Deps} -> 100 | Deps 101 | end. 102 | 103 | generate_sys_config(Dir, EnvName) -> 104 | %% Generate sys.config from template.sys.config depending on the env 105 | TemplateSysConfig = filename:join(Dir, "config/template.sys.config"), 106 | case filelib:is_file(TemplateSysConfig) of 107 | true -> 108 | process_template(Dir, EnvName); 109 | false -> 110 | ok 111 | end, 112 | %% Apply override vars if there is such a file, and there is sys.config 113 | SysConfig = filename:join(Dir, "config/sys.config"), 114 | Override = filename:join(Dir, "config/override.vars"), 115 | case filelib:is_file(SysConfig) andalso filelib:is_file(Override) of 116 | true -> 117 | apply_override(SysConfig, Override, EnvName); 118 | false -> 119 | ok 120 | end. 121 | 122 | %% Generate sys.config from template.sys.config + config.vars 123 | %% If template is missing, it left sys.config as it is (no templating there) 124 | %% If config.vars is missing, it copies template to sys.config 125 | process_template(Dir, EnvName) -> 126 | VarsFile = filename:join(Dir, "config/config.vars"), 127 | Template = filename:join(Dir, "config/template.sys.config"), 128 | SysConf = filename:join(Dir, "config/sys.config"), 129 | Vars = read_vars(VarsFile, EnvName), 130 | case file:read_file(Template) of 131 | {ok, File} -> 132 | case re:run(File, <<"\\${(.*?)}">>, 133 | [{capture, all_but_first, binary}, global]) of 134 | nomatch -> 135 | file:copy(Template, SysConf); 136 | {match, Vs} -> 137 | %% Get the ${var} variable names 138 | Vss = [binary_to_atom(V, utf8) || [V] <- Vs], 139 | %% Replace them 140 | Out = lists:foldl( 141 | fun(Var, Acc) -> 142 | case lists:keyfind(Var, 1, Vars) of 143 | {Var, Value} -> 144 | LV = case Value of 145 | _ when is_integer(Value) -> 146 | integer_to_list(Value); 147 | _ when is_atom(Value) -> 148 | atom_to_list(Value); 149 | _ -> 150 | "\"" ++ Value ++ "\"" 151 | end, 152 | Re = "\\${" ++ atom_to_list(Var) ++ "}", 153 | re:replace(Acc, Re, LV, [global]); 154 | false -> 155 | err("No value for ~p~n", [Var]) 156 | end 157 | end, File, Vss), 158 | %% Write the output 159 | file:write_file(SysConf, Out) 160 | end; 161 | {error, enoent} -> 162 | %% No template, leave sys.config as it is 163 | ok 164 | end. 165 | 166 | apply_override(SysConfig, Override, EnvName) -> 167 | Env = list_to_atom(EnvName), 168 | {ok, [Sys]} = file:consult(SysConfig), 169 | {ok, Over} = file:consult(Override), 170 | case lists:keyfind(Env, 1, Over) of 171 | false -> 172 | ok; 173 | {_, Vars} -> 174 | Sys2 = lists:foldl( 175 | fun(Var, Acc) -> 176 | apply_path(Acc, Var) 177 | end, Sys, Vars), 178 | dump_configs(SysConfig, Sys2) 179 | end. 180 | 181 | atom_tokens(String) -> 182 | [list_to_atom(T) || T <- string:tokens(String, ".")]. 183 | 184 | %% Deep delete/replace a "key1.key2.key3" path in a term 185 | apply_path(Term, {KeyPath, Value}) -> 186 | apply_path2(Term, atom_tokens(KeyPath), store, Value); 187 | apply_path(Term, KeyPath) -> 188 | apply_path2(Term, atom_tokens(KeyPath), del, undefined). 189 | 190 | %% Deep delete/replace a key list in a term 191 | apply_path2(Term , [], _Op, _Value) -> 192 | Term; 193 | apply_path2(Term, [Key], store, Value) -> 194 | lists:keystore(Key, 1, Term, {Key, Value}); 195 | apply_path2(Term, [Key], del, _Value) -> 196 | lists:keydelete(Key, 1, Term); 197 | apply_path2(Term, [Key | Rest], Op, Value) -> 198 | SubTerm = case lists:keyfind(Key, 1, Term) of 199 | false -> 200 | []; 201 | {Key, V} -> 202 | V 203 | end, 204 | R = apply_path2(SubTerm, Rest, Op, Value), 205 | lists:keystore(Key, 1, Term, {Key, R}). 206 | 207 | extract_dep_config(Dep) -> 208 | case file:consult(filename:join(["deps", Dep, "config/sys.config"])) of 209 | {ok, [Term]} -> 210 | Key = list_to_atom(Dep), 211 | lists:filter( 212 | fun({K, _}) when K =:= Key -> true; 213 | (_) -> false 214 | end, Term); 215 | _ -> 216 | [] 217 | end. 218 | 219 | apply_main_sys_config(DepConfigs) -> 220 | {ok, [Main]} = file:consult("config/sys.config"), 221 | %% Override the keys in DepConfigs with sys.config 222 | Result = lists:foldl( 223 | fun({Key, Value}, Acc) -> 224 | case lists:keyfind(Key, 1, Main) of 225 | false -> 226 | %% New key, wasn't in the main sys.config 227 | %% Let us add it to that 228 | [{Key, Value} | Acc]; 229 | _ -> 230 | %% Main sys.config overrides this key, 231 | %% so do nothing here 232 | Acc 233 | end 234 | end, Main, DepConfigs), 235 | lists:keysort(1, Result). 236 | 237 | dump_configs(Filename, Configs) -> 238 | {ok, Dev} = file:open(Filename, [write]), 239 | io:format(Dev, "~p.", [Configs]), 240 | file:close(Dev). 241 | 242 | generate_deps(Deps, Env) -> 243 | [generate_sys_config("deps/" ++ Dep, Env) || Dep <- Deps]. 244 | 245 | read_vars(VarsFile, EnvName) -> 246 | case file:consult(VarsFile) of 247 | {ok, AllVars} -> 248 | Env = list_to_atom(EnvName), 249 | case lists:keyfind(Env, 1, AllVars) of 250 | {Env, Vars} -> 251 | Vars; 252 | _ -> 253 | io:format("Could not find vars for env:~p~n", [EnvName]), 254 | [] 255 | end; 256 | {error, enoent} -> 257 | []; 258 | ELSE -> 259 | io:format("heyhey:~p~n", [ELSE]), 260 | [] 261 | end. 262 | 263 | err(Msg, Args) -> 264 | io:format(Msg, Args), 265 | init:halt(1). 266 | 267 | -------------------------------------------------------------------------------- /generic_app/config/sys.config: -------------------------------------------------------------------------------- 1 | [{app,[{timer_sleep,5000}]}, 2 | {csi, 3 | [{servers, 4 | [{app_service,app_service,[], 5 | {app_service, 6 | {csi,start_link,[app_service,app_service,[]]}, 7 | permanent,2000,worker, 8 | [app,app_service]}}]}]}]. -------------------------------------------------------------------------------- /generic_app/config/template.sys.config: -------------------------------------------------------------------------------- 1 | %% 2 | %% With the template sys.config we can define a generic sys.config which 3 | %% will be processed later by the generate-config.sh script. 4 | %% During the processing the variable references will be substituted with 5 | %% the actual content of the variables depending on the build environment. 6 | [{app, [ 7 | {timer_sleep, ${timer_sleep}} 8 | ]}, 9 | {csi,[{servers,[{app_service,app_service,[], 10 | {app_service, 11 | {csi, 12 | start_link, 13 | [app_service, app_service, []]}, 14 | permanent, 15 | 2000, 16 | worker, 17 | [app,app_service]}}] 18 | } 19 | ] 20 | } 21 | %% Use this from R18+ only 22 | %%[{csi,[{servers,[{app_service,app_service,[], 23 | %% #{id => app_service, 24 | %% start => {csi, 25 | %% start_link, 26 | %% [app_service, app_service, []]}, 27 | %% restart => permanent, 28 | %% shutdown => 2000, 29 | %% type => worker, 30 | %% modules => [app,app_service]}}] 31 | %% } 32 | %% ] 33 | %% } 34 | ]. 35 | -------------------------------------------------------------------------------- /generic_app/config/vm.args: -------------------------------------------------------------------------------- 1 | ## Name of the node 2 | -name app_server 3 | 4 | ## Cookie for distributed erlang 5 | -setcookie csi_server 6 | 7 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 8 | ## (Disabled by default..use with caution!) 9 | ##-heart 10 | 11 | ## Enable kernel poll and a few async threads 12 | ##+K true 13 | ##+A 5 14 | 15 | ## Increase number of concurrent ports/sockets 16 | ##-env ERL_MAX_PORTS 4096 17 | 18 | ## Tweak GC to run more often 19 | ##-env ERL_FULLSWEEP_AFTER 10 20 | -hidden 21 | -connect_all false 22 | 23 | 24 | -------------------------------------------------------------------------------- /generic_app/rebar.config: -------------------------------------------------------------------------------- 1 | {deps_dir, "deps"}. 2 | 3 | {erl_opts, [debug_info, 4 | {parse_transform, lager_transform}, 5 | {define, lager} 6 | ]}. 7 | {deps, [ 8 | {lager, {git, "git@github.com:basho/lager.git", {tag, "3.0.2"}}}, 9 | {csi, {git, "git@github.com:esl/CommonServiceInterface.git", {branch, "master"}}} 10 | ]}. 11 | 12 | {pre_hooks, [{compile, "config/generate-config.sh --deps ${BUILD_DEPS} --build-env ${BUILD_ENV}"}]}. 13 | 14 | {relx, [{release, {csi_example, "0.0.1"}, 15 | [runtime_tools, 16 | csi 17 | ]}, 18 | {dev_mode, false}, 19 | {include_erts, true}, 20 | {sys_config, "config/sys.config"}, 21 | {vm_args, "config/vm.args"}, 22 | {extended_start_script, true}]}. 23 | 24 | {dialyzer, [ 25 | {warnings, [underspecs, no_return]}, 26 | {get_warnings, true}, 27 | {plt_apps, top_level_deps}, % top_level_deps | all_deps 28 | {plt_extra_apps, [lager]}, 29 | {plt_location, local}, % local | "/my/file/name" 30 | {plt_prefix, "rebar3"}, 31 | {base_plt_apps, [stdlib, kernel]}, 32 | {base_plt_location, global}, % global | "/my/file/name" 33 | {base_plt_prefix, "rebar3"} 34 | ]}. 35 | 36 | -------------------------------------------------------------------------------- /generic_app/rebar.config.script: -------------------------------------------------------------------------------- 1 | 2 | %% In dev, release dependencies will be symlinked in the _rel//lib 3 | Env = os:getenv("BUILD_ENV"), 4 | %io:format("Build env is ~p~n", [Env]), 5 | %io:format("CONFIG is:~p~n", [CONFIG]), 6 | 7 | Symlink = case Env of 8 | false -> 9 | true; 10 | "dev" -> 11 | true; 12 | "local" -> 13 | true; 14 | _ -> 15 | false 16 | end, 17 | 18 | case Symlink of 19 | true -> 20 | % io:format("relx dev_mode enabled, deps are symlinked~n", []), 21 | [{dev_mode, true} | CONFIG]; 22 | false -> 23 | CONFIG 24 | end. 25 | 26 | -------------------------------------------------------------------------------- /generic_app/relx.config: -------------------------------------------------------------------------------- 1 | {release, {your_application_name, "0.0.1"}, 2 | [csi, 3 | app, 4 | runtime_tools]}. 5 | {sys_config,"./config/sys.config"}. 6 | {vm_args,"./config/vm.args"}. 7 | {extended_start_script, true}. -------------------------------------------------------------------------------- /generic_app/src/app.app.src: -------------------------------------------------------------------------------- 1 | {application, app, [ 2 | {description, "Your Application service"}, 3 | {vsn, "0.0.1"}, 4 | {id, "git"}, 5 | {modules, [app,app_service]}, 6 | {registered, []}, 7 | {applications, [ 8 | kernel, 9 | compiler, 10 | stdlib, 11 | syntax_tools, 12 | csi 13 | ]}, 14 | {env, []} 15 | ]}. 16 | -------------------------------------------------------------------------------- /generic_app/src/app.erl: -------------------------------------------------------------------------------- 1 | -module(app). 2 | 3 | -define(SERVICE_NAME, app_service). 4 | -define(SERVICE_MODULE, app_service). 5 | 6 | %% ==================================================================== 7 | %% API functions 8 | %% ==================================================================== 9 | 10 | -export([process_foo/1, 11 | process_too_long/1, 12 | process_crashing/1]). 13 | 14 | process_foo(Atom) -> csi:call_p(?SERVICE_NAME, process_foo, [Atom]). 15 | process_too_long(Atom) -> csi:call_p(?SERVICE_NAME, process_too_long, [Atom]). 16 | process_crashing(Atom) -> csi:call_p(?SERVICE_NAME, process_crashing, [Atom]). 17 | -------------------------------------------------------------------------------- /generic_app/src/app_service.erl: -------------------------------------------------------------------------------- 1 | -module(app_service). 2 | -behaviour(csi_server). 3 | 4 | %% General state of the service 5 | -record(app_state,{}). 6 | 7 | %% Lifecycle State for every requests' 8 | -record(app_session_state,{}). 9 | 10 | -export([init_service/1, 11 | init/2, 12 | terminate/2, 13 | terminate_service/2]). 14 | 15 | -export([process_foo/2, 16 | process_too_long/2, 17 | process_crashing/2]). 18 | 19 | 20 | %% ==================================================================== 21 | %% Behavioural functions 22 | %% ==================================================================== 23 | init_service(_InitArgs) -> 24 | {ok,#app_state{}}. 25 | 26 | init(_Args,_ServiceState) -> 27 | {ok,#app_session_state{}}. 28 | 29 | terminate(_Reason,_State) -> 30 | ok. 31 | 32 | terminate_service(_Reason,_State) -> 33 | ok. 34 | 35 | %% ==================================================================== 36 | %% Service functions 37 | %% ==================================================================== 38 | process_foo(_Args,State) -> 39 | {hello_world,State}. 40 | 41 | process_too_long(_Args,State) -> 42 | timer:sleep(100000), 43 | {long_job_fininshed,State}. 44 | 45 | process_crashing(Args,State) -> 46 | A = Args - Args, 47 | {A,State}. 48 | -------------------------------------------------------------------------------- /include/csi.hrl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -define(CSI_SERVICE_NAME,csi_service). 11 | -define(CSI_APPLICATION_NAME,csi). 12 | -define(DEFAULT_STATS_MODULE,csi_stats). 13 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps_dir, "deps"}. 2 | 3 | {erl_opts, [debug_info, 4 | {parse_transform, lager_transform}, 5 | {define, lager} 6 | ]}. 7 | {deps, [ 8 | {lager, {git, "git@github.com:basho/lager.git", {branch, "3.0.2"}}} 9 | ]}. 10 | 11 | {pre_hooks, [{compile, "config/generate-config.sh --deps ${BUILD_DEPS} --build-env ${BUILD_ENV}"}]}. 12 | 13 | {relx, [{release, {csi_server, "0.0.1"}, 14 | [runtime_tools, 15 | csi 16 | ]}, 17 | {dev_mode, false}, 18 | {include_erts, true}, 19 | {sys_config, "config/sys.config"}, 20 | {vm_args, "config/vm.args"}, 21 | {extended_start_script, true}]}. 22 | 23 | {dialyzer, [ 24 | {warnings, [underspecs, no_return]}, 25 | {get_warnings, true}, 26 | {plt_apps, top_level_deps}, % top_level_deps | all_deps 27 | {plt_extra_apps, [lager]}, 28 | {plt_location, local}, % local | "/my/file/name" 29 | {plt_prefix, "rebar3"}, 30 | {base_plt_apps, [stdlib, kernel]}, 31 | {base_plt_location, global}, % global | "/my/file/name" 32 | {base_plt_prefix, "rebar3"} 33 | ]}. 34 | 35 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | 2 | %% In dev, release dependencies will be symlinked in the _rel//lib 3 | Env = os:getenv("BUILD_ENV"), 4 | %io:format("Build env is ~p~n", [Env]), 5 | %io:format("CONFIG is:~p~n", [CONFIG]), 6 | 7 | Symlink = case Env of 8 | false -> 9 | true; 10 | "dev" -> 11 | true; 12 | "local" -> 13 | true; 14 | _ -> 15 | false 16 | end, 17 | 18 | case Symlink of 19 | true -> 20 | % io:format("relx dev_mode enabled, deps are symlinked~n", []), 21 | [{dev_mode, true} | CONFIG]; 22 | false -> 23 | CONFIG 24 | end. 25 | 26 | -------------------------------------------------------------------------------- /src/csi.app.src: -------------------------------------------------------------------------------- 1 | {application, csi, [ 2 | {description, "Common Service Interface service and library."}, 3 | {vsn, "0.0.1"}, 4 | {id, "git"}, 5 | {modules, []}, 6 | {registered, []}, 7 | {applications, [ 8 | kernel, 9 | stdlib, 10 | lager 11 | ]}, 12 | {mod, {csi_app, []}}, 13 | {env, []} 14 | ]}. 15 | -------------------------------------------------------------------------------- /src/csi.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -module(csi). 11 | -compile([{parse_transform, lager_transform}, {export_all}]). 12 | 13 | -include("csi.hrl"). 14 | -include("csi_common.hrl"). 15 | %% ==================================================================== 16 | %% API functions 17 | %% ==================================================================== 18 | -export([start/0, 19 | start_link/0, 20 | stop/0]). 21 | 22 | -export([ start/2, 23 | start_link/2, 24 | start/3, 25 | start_link/3, 26 | start/4, 27 | start_link/4, 28 | stop/1, 29 | services/0, 30 | services_status/0, 31 | service_status/1, 32 | call_p/2, 33 | call_p/3, 34 | call_p/4, 35 | call_s/2, 36 | call_s/3, 37 | call_s/4, 38 | call/3, 39 | call/4, 40 | cast_p/3, 41 | cast_p/4, 42 | call_p/5, 43 | cast/2, 44 | cast/3, 45 | post_p/3, 46 | post_p/4, 47 | stats_start/0, 48 | stats_start/1, 49 | stats_stop/0, 50 | stats_stop/1, 51 | stats_include_funs/2, 52 | stats_exclude_funs/2, 53 | stats_set_funs/2, 54 | stats_get_all/1, 55 | stats_get_funs/2, 56 | stats_get_types/2, 57 | stats_get_specific/3, 58 | stats_include_type/2, 59 | stats_exclude_type/2, 60 | stats_get_process_table/1, 61 | stats_change_module/2, 62 | stats_params/1, 63 | stats_params/2, 64 | stats_param_get/3, 65 | stats_param_set/4, 66 | register/0, 67 | unregister/0, 68 | set_options/1, 69 | set_options/2 70 | ]). 71 | 72 | -export([list_macros/0, 73 | process_foo_call_p/1, 74 | process_too_long_call_p/1, 75 | process_crashing_call_p/1, 76 | process_foo_call_s/1, 77 | process_too_long_call_s/1, 78 | process_crashing_call_s/1, 79 | process_foo_call/1, 80 | process_too_long_call/1, 81 | process_crashing_call/1, 82 | process_foo_post_p/1, 83 | process_too_long_post_p/1, 84 | process_crashing_post_p/1, 85 | process_foo_cast/1, 86 | process_too_long_cast/1, 87 | process_crashing_cast/1 88 | ]). 89 | 90 | 91 | %% start/0 92 | %% ==================================================================== 93 | %% @doc starts the Common Service Interface service 94 | %% @end 95 | -spec start() -> Result when 96 | Result :: {ok, Pid} | ignore | {error, Error}, 97 | Pid :: pid(), 98 | Error :: {already_started, Pid} | term(). 99 | %% ==================================================================== 100 | start() -> start(?CSI_SERVICE_NAME, ?CSI_SERVICE_MODULE). 101 | 102 | %% start_link/0 103 | %% ==================================================================== 104 | %% @doc starts the Common Service Interface service and make a link 105 | %% @end 106 | -spec start_link() -> Result when 107 | Result :: {ok, Pid} | ignore | {error, Error}, 108 | Pid :: pid(), 109 | Error :: {already_started, Pid} | term(). 110 | %% ==================================================================== 111 | start_link() -> start_link(?CSI_SERVICE_NAME, ?CSI_SERVICE_MODULE). 112 | 113 | %% stop/0 114 | %% ==================================================================== 115 | %% @doc stops the Common Service Interface service 116 | %% @end 117 | -spec stop() -> Reply when 118 | Reply :: term(). 119 | %% ==================================================================== 120 | stop() -> stop(?CSI_SERVICE_NAME). 121 | 122 | %% start/2 123 | %% ==================================================================== 124 | %% @doc starts a service through the Common Service Interface service 125 | %% @end 126 | -spec start(ServerName :: atom(), 127 | Module :: atom()) -> Result when 128 | Result :: {ok, Pid} | ignore | {error, Error}, 129 | Pid :: pid(), 130 | Error :: {already_started, Pid} | term(). 131 | %% ==================================================================== 132 | start(ServerName, Module) -> 133 | start(ServerName, Module, undefined). 134 | 135 | %% start/3 136 | %% ==================================================================== 137 | %% @doc starts a service through the Common Service Interface service 138 | %% @end 139 | -spec start(ServerName :: atom(), 140 | Module :: atom(), 141 | InitArgs :: term()) -> Result when 142 | Result :: {ok, Pid} | ignore | {error, Error}, 143 | Pid :: pid(), 144 | Error :: {already_started, Pid} | term(). 145 | %% ==================================================================== 146 | start(ServerName, Module, InitArgs) -> 147 | start(ServerName, Module, InitArgs, ?CSI_DEFAULT_OPTIONS). 148 | 149 | %% start/4 150 | %% ==================================================================== 151 | %% @doc starts a service through the Common Service Interface service 152 | %% @end 153 | -spec start(ServerName :: atom(), 154 | Module :: atom(), 155 | InitArgs :: term(), 156 | Options :: property_list()) -> Result when 157 | Result :: {ok, Pid} | ignore | {error, Error}, 158 | Pid :: pid(), 159 | Error :: {already_started, Pid} | term(). 160 | %% ==================================================================== 161 | start(ServerName, Module, InitArgs, Options) -> 162 | gen_server:start({local, ServerName}, 163 | ?CSI_SERVER_MODULE, 164 | {ServerName, Module, InitArgs, Options}, 165 | []). 166 | 167 | %% start_link/2 168 | %% ==================================================================== 169 | %% @doc starts a service through the Common Service Interface service with link 170 | %% @end 171 | -spec start_link(ServerName :: atom(), 172 | Module :: atom()) -> Result when 173 | Result :: {ok, Pid} | ignore | {error, Error}, 174 | Pid :: pid(), 175 | Error :: {already_started, Pid} | term(). 176 | %% ==================================================================== 177 | 178 | start_link(ServerName, Module) -> 179 | start_link(ServerName, Module, undefined). 180 | 181 | %% start_link/3 182 | %% ==================================================================== 183 | %% @doc starts a service through the Common Service Interface service 184 | %% @end 185 | -spec start_link(ServerName :: atom(), 186 | Module :: atom(), 187 | InitArgs :: term()) -> Result when 188 | Result :: {ok, Pid} | ignore | {error, Error}, 189 | Pid :: pid(), 190 | Error :: {already_started, Pid} | term(). 191 | %% ==================================================================== 192 | start_link(ServerName, Module, InitArgs) -> 193 | start_link(ServerName, Module, InitArgs, ?CSI_DEFAULT_OPTIONS). 194 | 195 | %% start_link/4 196 | %% ==================================================================== 197 | %% @doc starts a service through the Common Service Interface service 198 | %% @end 199 | -spec start_link(ServerName :: atom(), 200 | Module :: atom(), 201 | InitArgs :: term(), 202 | Options :: property_list()) -> Result when 203 | Result :: {ok, Pid} | ignore | {error, Error}, 204 | Pid :: pid(), 205 | Error :: {already_started, Pid} | term(). 206 | %% ==================================================================== 207 | start_link(ServerName, Module, InitArgs, Options) -> 208 | gen_server:start_link({local, ServerName}, 209 | ?CSI_SERVER_MODULE, 210 | {ServerName, Module, InitArgs, Options}, 211 | []). 212 | 213 | %% stop/1 214 | %% ==================================================================== 215 | %% @doc stops a service through the Common Service Interface service 216 | %% @end 217 | -spec stop(ServerName :: atom()) -> Reply when 218 | Reply :: term(). 219 | %% ==================================================================== 220 | stop(ServerName) -> 221 | gen_server:call(ServerName, 222 | stop, 223 | ?DEFAULT_CLIENT_TIMEOUT). 224 | 225 | %% services/0 226 | %% ==================================================================== 227 | %% @doc lists registered services for CSI 228 | %% @end 229 | -spec services() -> Reply when 230 | Reply :: term(). %list({registered_name, atom()}). 231 | %% ==================================================================== 232 | services() -> 233 | csi:call_s(?CSI_SERVICE_NAME, 234 | services). 235 | 236 | %% services_status/0 237 | %% ==================================================================== 238 | %% @doc collect all CSI services status 239 | %% @end 240 | -spec services_status() -> Reply when 241 | Reply :: list({{registered_name, atom()}, csi_server:csi_service_state()}). 242 | %% ==================================================================== 243 | %% [{{registered_name, csi_service}, 244 | %% {csi_service_state, csi_service, csi_service, 245 | %% {csi_service_state}, 246 | %% true, 20500, 24597, csi_server, 247 | %% [all], 248 | %% [], 249 | %% [{response_time, [{"last_nth_to_collect", 10}, 250 | %% {"normalize_to_nth", 8}]}]}}] 251 | services_status() -> 252 | gen_server:call(?CSI_SERVICE_NAME, 253 | '$collect_services_status', 254 | ?DEFAULT_CLIENT_TIMEOUT). 255 | 256 | %% service_status/1 257 | %% ==================================================================== 258 | %% @doc returns a CSI service status 259 | %% @end 260 | -spec service_status(ServiceName :: atom()) -> Reply when 261 | Reply :: csi_server:csi_service_state(). 262 | %% ==================================================================== 263 | service_status(ServerName) -> 264 | gen_server:call(ServerName, 265 | '$service_status', 266 | ?DEFAULT_CLIENT_TIMEOUT). 267 | 268 | %% stats_start/0 269 | %% ==================================================================== 270 | %% @doc turns collecting statistics for all CSI services 271 | %% @end 272 | -spec stats_start() -> Reply when 273 | Reply :: term(). 274 | %% ==================================================================== 275 | stats_start() -> 276 | gen_server:call(?CSI_SERVICE_NAME, 277 | '$stats_start_all', 278 | ?DEFAULT_CLIENT_TIMEOUT). 279 | 280 | %% stats_start/1 281 | %% ==================================================================== 282 | %% @doc turns collecting statistics for a named CSI service 283 | %% @end 284 | -spec stats_start(ServerName :: atom()) -> Reply when 285 | Reply :: term(). 286 | %% ==================================================================== 287 | stats_start(ServerName) -> 288 | gen_server:call(ServerName, 289 | '$stats_start', 290 | ?DEFAULT_CLIENT_TIMEOUT). 291 | 292 | %% stats_stop/0 293 | %% ==================================================================== 294 | %% @doc turns off collecting statistics for all CSI services 295 | %% @end 296 | -spec stats_stop() -> Reply when 297 | Reply :: term(). 298 | %% ==================================================================== 299 | stats_stop() -> 300 | gen_server:call(?CSI_SERVICE_NAME, 301 | '$stats_stop_all', 302 | ?DEFAULT_CLIENT_TIMEOUT). 303 | 304 | %% stats_stop/1 305 | %% ==================================================================== 306 | %% @doc turns off collecting statistics for a named CSI service 307 | %% @end 308 | -spec stats_stop(ServerName :: atom()) -> Reply when 309 | Reply :: term(). 310 | %% ==================================================================== 311 | stats_stop(ServerName) -> 312 | gen_server:call(ServerName, 313 | '$stats_stop', 314 | ?DEFAULT_CLIENT_TIMEOUT). 315 | 316 | %% stats_include_funs/2 317 | %% ==================================================================== 318 | %% @doc include functions to collect stats for 319 | %% @end 320 | -spec stats_include_funs(ServerName :: atom(), 321 | FunctionList :: list(atom())) -> Reply when 322 | Reply :: term(). 323 | %% ==================================================================== 324 | stats_include_funs(ServerName, FunctionList) -> 325 | gen_server:call(ServerName, {'$stats_include_funs', 326 | FunctionList}, 327 | ?DEFAULT_CLIENT_TIMEOUT). 328 | 329 | %% stats_exclude_funs/2 330 | %% ==================================================================== 331 | %% @doc exclude functions from included functions to collect stats for 332 | %% @end 333 | -spec stats_exclude_funs(ServerName :: atom(), 334 | FunctionList :: list(atom())) -> Reply when 335 | Reply :: term(). 336 | %% ==================================================================== 337 | stats_exclude_funs(ServerName, FunctionList) -> 338 | gen_server:call(ServerName, 339 | {'$stats_exclude_funs', FunctionList}, 340 | ?DEFAULT_CLIENT_TIMEOUT). 341 | 342 | %% stats_get_all/1 343 | %% ==================================================================== 344 | %% @doc get all statistics for a CSI service 345 | %% @end 346 | -spec stats_get_all(ServerName :: atom()) -> Reply when 347 | Reply :: list({{Type :: atom(), Function :: atom()}, tuple()}). 348 | %% ==================================================================== 349 | %% [{{response_time, process_foo}, {1, 69, 69.0, 69, 69}}] 350 | stats_get_all(ServerName) -> 351 | gen_server:call(ServerName, 352 | '$stats_get_all', 353 | ?DEFAULT_CLIENT_TIMEOUT). 354 | 355 | %% stats_get_funs/2 356 | %% ==================================================================== 357 | %% @doc get statistics for functions in a CSI service 358 | %% @end 359 | -spec stats_get_funs(ServerName :: atom(), 360 | FunctionList :: list(atom()) | atom()) -> Reply when 361 | Reply :: list({{Type :: atom(), Function :: atom()}, tuple()}). 362 | %% ==================================================================== 363 | stats_get_funs(ServerName, FunctionList) 364 | when is_atom(FunctionList) -> 365 | stats_get_funs(ServerName, [FunctionList]); 366 | 367 | stats_get_funs(ServerName, FunctionList) 368 | when is_list(FunctionList) -> 369 | gen_server:call(ServerName, 370 | {'$stats_get_funs', FunctionList}, 371 | ?DEFAULT_CLIENT_TIMEOUT). 372 | 373 | %% stats_get_types/2 374 | %% ==================================================================== 375 | %% @doc get statistics for a given type in a CSI service 376 | %% @end 377 | -spec stats_get_types(ServerName :: atom(), 378 | FunctionList :: list(atom()) | atom()) -> Reply when 379 | Reply :: list({{Type :: atom(), Function :: atom()}, tuple()}). 380 | %% ==================================================================== 381 | stats_get_types(ServerName, TypeList) 382 | when is_atom(TypeList) -> 383 | stats_get_types(ServerName, [TypeList]); 384 | 385 | stats_get_types(ServerName, TypeList) 386 | when is_list(TypeList) -> 387 | gen_server:call(ServerName, 388 | {'$stats_get_types', TypeList}, 389 | ?DEFAULT_CLIENT_TIMEOUT). 390 | 391 | %% stats_get_specific/3 392 | %% ==================================================================== 393 | %% @doc get statistics for a given type and a given function in a CSI service 394 | %% @end 395 | -spec stats_get_specific(ServerName :: atom(), 396 | Function :: atom(), 397 | Type :: atom()) -> Reply when 398 | Reply :: list({{Type :: atom(), Function :: atom()}, tuple()}). 399 | %% ==================================================================== 400 | stats_get_specific(ServerName, Function, Type) -> 401 | gen_server:call(ServerName, 402 | {'$stats_get_specific', Function, Type}, 403 | ?DEFAULT_CLIENT_TIMEOUT). 404 | 405 | %% stats_get_process_table/1 406 | %% ==================================================================== 407 | %% @doc get the process table for a CSI service 408 | %% @end 409 | -spec stats_get_process_table(ServerName :: atom()) -> Reply when 410 | Reply :: list(tuple()). 411 | %% ==================================================================== 412 | stats_get_process_table(ServerName) -> 413 | gen_server:call(ServerName, 414 | '$stats_get_process_table', 415 | ?DEFAULT_CLIENT_TIMEOUT). 416 | 417 | %% stats_set_funs/2 418 | %% ==================================================================== 419 | %% @doc sets the list of functions to be included for collecting stats 420 | %% @end 421 | -spec stats_set_funs(ServerName :: atom(), 422 | FunctionList :: list(atom())) -> Reply when 423 | Reply :: term(). 424 | %% ==================================================================== 425 | stats_set_funs(ServerName, FunctionList) 426 | when is_list(FunctionList) -> 427 | gen_server:call(ServerName, 428 | {'$stats_set_funs', FunctionList}, 429 | ?DEFAULT_CLIENT_TIMEOUT). 430 | 431 | %% stats_set_param/4 432 | %% ==================================================================== 433 | %% @doc sets a parameter for a given statistic type 434 | %% @end 435 | -spec stats_param_set(ServerName :: atom(), 436 | Type :: atom(), 437 | Parameter :: term(), 438 | Value :: term()) -> Reply when 439 | Reply :: term(). 440 | %% ==================================================================== 441 | stats_param_set(ServerName, Type, Parameter, Value) -> 442 | gen_server:call(ServerName, 443 | {'$stats_param_set', Type, Parameter, Value}, 444 | ?DEFAULT_CLIENT_TIMEOUT). 445 | 446 | %% stats_param_get/3 447 | %% ==================================================================== 448 | %% @doc get a specific parameter for a given statistic type 449 | %% @end 450 | -spec stats_param_get(ServerName :: atom(), 451 | Type :: atom(), 452 | Parameter :: term()) -> Reply when 453 | Reply :: term(). 454 | %% ==================================================================== 455 | stats_param_get(ServerName, Type, Parameter) -> 456 | gen_server:call(ServerName, 457 | {'$stats_param_get', Type, Parameter}, 458 | ?DEFAULT_CLIENT_TIMEOUT). 459 | 460 | %% stats_params/1 461 | %% ==================================================================== 462 | %% @doc get all parameters for a given statistic type 463 | %% @end 464 | -spec stats_params(ServerName :: atom()) -> Reply when 465 | Reply :: list(proplists:property()). 466 | %% ==================================================================== 467 | stats_params(ServerName) -> 468 | gen_server:call(ServerName, 469 | {'$stats_params'}, 470 | ?DEFAULT_CLIENT_TIMEOUT). 471 | 472 | 473 | %% stats_params/2 474 | %% ==================================================================== 475 | %% @doc get all parameters for a given statistic type 476 | %% @end 477 | -spec stats_params(ServerName :: atom(), 478 | Type :: atom()) -> Reply when 479 | Reply :: list(proplists:property()). 480 | %% ==================================================================== 481 | stats_params(ServerName, Type) -> 482 | gen_server:call(ServerName, 483 | {'$stats_params', Type}, 484 | ?DEFAULT_CLIENT_TIMEOUT). 485 | 486 | %% stats_include_type/2 487 | %% ==================================================================== 488 | %% @doc include a statistic type in the collecting metrics 489 | %% The stats_module shall containg the function named exactly as it is 490 | %% in the collected statistics type list 491 | %% @end 492 | -spec stats_include_type(ServerName :: atom(), 493 | Type :: atom()) -> Reply when 494 | Reply :: term(). 495 | %% ==================================================================== 496 | stats_include_type(ServerName, Type) 497 | when is_atom(Type) -> 498 | gen_server:call(ServerName, 499 | {'$stats_include_type', Type}, 500 | ?DEFAULT_CLIENT_TIMEOUT). 501 | 502 | %% stats_exclude_type/2 503 | %% ==================================================================== 504 | %% @doc stop collecting a statistic type for a service 505 | %% @end 506 | -spec stats_exclude_type(ServerName :: atom(), 507 | Type :: atom()) -> Reply when 508 | Reply :: term(). 509 | %% ==================================================================== 510 | stats_exclude_type(ServerName, Type) 511 | when is_atom(Type) -> 512 | gen_server:call(ServerName, 513 | {'$stats_exclude_type', Type}, 514 | ?DEFAULT_CLIENT_TIMEOUT). 515 | 516 | %% stats_change_module/2 517 | %% ==================================================================== 518 | %% @doc sets the module name for a service where the statistical functions are 519 | %% @end 520 | -spec stats_change_module(ServerName :: atom(), 521 | Module :: atom()) -> Reply when 522 | Reply :: term(). 523 | %% ==================================================================== 524 | stats_change_module(ServerName, Module) -> 525 | gen_server:call(ServerName, 526 | {'$stats_change_module', Module}, 527 | ?DEFAULT_CLIENT_TIMEOUT). 528 | 529 | %% call_p/2 530 | %% ==================================================================== 531 | %% @doc call a service function parallel 532 | %% @end 533 | -spec call_p(ServerName :: atom(), 534 | Request :: atom()) -> Reply when 535 | Reply :: term(). 536 | %% ==================================================================== 537 | call_p(ServerName, Request) -> 538 | call_p(ServerName, Request, [], ?DEFAULT_SERVER_TIMEOUT). 539 | 540 | %% call_p/3 541 | %% ==================================================================== 542 | %% @doc call a service function parallel with arguments 543 | %% @end 544 | -spec call_p(ServerName :: atom(), 545 | Request :: atom(), 546 | Args :: term()) -> Reply when 547 | Reply :: term(). 548 | %% ==================================================================== 549 | call_p(ServerName, Request, Args) -> 550 | call_p(ServerName, Request, Args, ?DEFAULT_SERVER_TIMEOUT). 551 | 552 | %% call_p/4 553 | %% ==================================================================== 554 | %% @doc call a service function parallel with arguments and timeout 555 | %% @end 556 | -spec call_p(ServerName :: atom(), 557 | Request :: atom(), 558 | Args :: term(), 559 | TimeoutForProcessing :: infinity | integer()) -> Reply when 560 | Reply :: term(). 561 | %% ==================================================================== 562 | call_p(ServerName, Request, Args, TimeoutForProcessing) -> 563 | call_p(ServerName, 564 | Request, 565 | Args, 566 | TimeoutForProcessing, 567 | ?DEFAULT_CLIENT_TIMEOUT). 568 | 569 | %% call_p/5 570 | %% ==================================================================== 571 | %% @doc call a service function parallel with arguments and timeouts 572 | %% @end 573 | -spec call_p(ServerName :: atom(), 574 | Request :: atom(), 575 | Args :: term(), 576 | TimeoutForProcessing :: infinity | integer(), 577 | ClientTimeout :: infinity | integer()) -> Reply when 578 | Reply :: term(). 579 | %% ==================================================================== 580 | call_p(ServerName, Request, Args, TimeoutForProcessing, ClientTimeout) -> 581 | csi_utils:call_server(ServerName, 582 | {call_p, Request, Args, TimeoutForProcessing}, 583 | ?DEFAULT_SERVICE_RETRY, 584 | ?DEFAULT_SERVICE_SLEEP, 585 | ClientTimeout). 586 | 587 | %% call_s/2 588 | %% ==================================================================== 589 | %% @doc call a service function serialized 590 | %% @end 591 | -spec call_s(ServerName :: atom(), 592 | Request :: atom()) -> Reply when 593 | Reply :: term(). 594 | %% ==================================================================== 595 | call_s(ServerName, Request) -> 596 | call_s(ServerName, Request, [], ?DEFAULT_CLIENT_TIMEOUT). 597 | 598 | %% call_s/3 599 | %% ==================================================================== 600 | %% @doc call a service function serialized with arguments 601 | %% @end 602 | -spec call_s(ServerName :: atom(), 603 | Request :: atom(), 604 | Args :: term()) -> Reply when 605 | Reply :: term(). 606 | %% ==================================================================== 607 | call_s(ServerName, Request, Args) -> 608 | call_s(ServerName, Request, Args, ?DEFAULT_CLIENT_TIMEOUT). 609 | 610 | %% call_s/4 611 | %% ==================================================================== 612 | %% @doc call a service function serialized with arguments and timeout 613 | %% @end 614 | -spec call_s(ServerName :: atom(), 615 | Request :: atom(), 616 | Args :: term(), 617 | TimeoutForProcessing :: infinity | integer()) -> Reply when 618 | Reply :: term(). 619 | %% ==================================================================== 620 | call_s(ServerName, Request, Args, TimeoutForProcessing) -> 621 | csi_utils:call_server(ServerName, 622 | {call_s, Request, Args}, 623 | ?DEFAULT_SERVICE_RETRY, 624 | ?DEFAULT_SERVICE_SLEEP, 625 | TimeoutForProcessing). 626 | 627 | %% call/3 628 | %% ==================================================================== 629 | %% @doc call the handle_call service function serialized 630 | %% @end 631 | -spec call(ServerName :: atom(), 632 | Request :: term(), 633 | Args :: term()) -> Reply when 634 | Reply :: term(). 635 | %% ==================================================================== 636 | call(ServerName, Request, Args) -> 637 | call(ServerName, Request, Args, ?DEFAULT_CLIENT_TIMEOUT). 638 | 639 | %% call/4 640 | %% ==================================================================== 641 | %% @doc call the handle_call service function serialized with timeout 642 | %% @end 643 | -spec call(ServerName :: atom(), 644 | Request :: term(), 645 | Args :: term(), 646 | TimeoutForProcessing :: infinity | integer()) -> Reply when 647 | Reply :: term(). 648 | %% ==================================================================== 649 | call(ServerName, Request, Args, TimeoutForProcessing) -> 650 | csi_utils:call_server(ServerName, 651 | {Request, Args}, 652 | ?DEFAULT_SERVICE_RETRY, 653 | ?DEFAULT_SERVICE_SLEEP, 654 | TimeoutForProcessing). 655 | 656 | %% post_p/3 657 | %% ==================================================================== 658 | %% @doc post a request parallel 659 | %% @end 660 | -spec post_p(ServerName :: atom(), 661 | Request :: atom(), 662 | Args :: term()) -> Reply when 663 | Reply :: term(). 664 | %% ==================================================================== 665 | post_p(ServerName, Request, Args) -> 666 | post_p(ServerName, Request, Args, ?DEFAULT_SERVER_TIMEOUT). 667 | 668 | %% post_p/4 669 | %% ==================================================================== 670 | %% @doc post a request parallel with timeout 671 | %% @end 672 | -spec post_p(ServerName :: atom(), 673 | Request :: atom(), 674 | Args :: term(), 675 | TimeoutForProcessing :: infinity | integer()) -> Reply when 676 | Reply :: term(). 677 | %% ==================================================================== 678 | post_p(ServerName, Request, Args, TimeoutForProcessing) -> 679 | csi_utils:call_server(ServerName, 680 | {post_p, Request, Args, TimeoutForProcessing}, 681 | ?DEFAULT_SERVICE_RETRY, 682 | ?DEFAULT_SERVICE_SLEEP, 683 | ?DEFAULT_CLIENT_TIMEOUT). 684 | %% @TODO implement cast_server in cu 685 | cast_p(ServerName, Request, Args) -> 686 | cast_p(ServerName, Request, Args, undefined). 687 | cast_p(ServerName, Request, Args, TimeoutForProcessing) -> 688 | csi_utils:call_server(ServerName, 689 | {cast_p, Request, Args, TimeoutForProcessing}, 690 | ?DEFAULT_SERVICE_RETRY, 691 | ?DEFAULT_SERVICE_SLEEP, 692 | ?DEFAULT_CLIENT_TIMEOUT). 693 | 694 | %% cast/2 695 | %% ==================================================================== 696 | %% @doc cast a service request 697 | %% @end 698 | -spec cast(ServerName :: atom(), 699 | Request :: term()) -> Reply when 700 | Reply :: term(). 701 | %% ==================================================================== 702 | cast(ServerName, Request) -> 703 | cast(ServerName, {cast, Request, []}). 704 | 705 | %% cast/3 706 | %% ==================================================================== 707 | %% @doc cast a service request 708 | %% @end 709 | -spec cast(ServerName :: atom(), 710 | Request :: term(), 711 | Args :: term()) -> Reply when 712 | Reply :: term(). 713 | %% ==================================================================== 714 | cast(ServerName, Request, Args) -> 715 | gen_server:cast(ServerName, 716 | {cast, Request, Args}). 717 | 718 | %% register/0 719 | %% ==================================================================== 720 | %% @doc registers a service in CSI 721 | %% @end 722 | -spec register() -> Reply when 723 | Reply :: ok 724 | | {error, {no_such_group, Name}}, 725 | Name :: any(). 726 | %% ==================================================================== 727 | register() -> 728 | pg2:join(?CSI_SERVICE_PROCESS_GROUP_NAME, self()). 729 | 730 | %% unregister/0 731 | %% ==================================================================== 732 | %% @doc unregisters a service in CSI 733 | %% @end 734 | -spec unregister() -> Reply when 735 | Reply :: ok 736 | | {error, {no_such_group, Name}}, 737 | Name :: any(). 738 | %% ==================================================================== 739 | unregister() -> 740 | pg2:leave(?CSI_SERVICE_PROCESS_GROUP_NAME, self()). 741 | 742 | %% set_options/1 743 | %% ==================================================================== 744 | %% @doc set options for the CSI service 745 | %% @end 746 | -spec set_options(Options :: property_list()) -> Reply when 747 | Reply :: term(). 748 | %% ==================================================================== 749 | set_options(Options) -> 750 | set_options(?CSI_SERVICE_NAME, Options). 751 | 752 | %% set_options/2 753 | %% ==================================================================== 754 | %% @doc set options for a service in CSI 755 | %% @end 756 | -spec set_options(ServerName :: atom(), 757 | Options :: property_list()) -> Reply when 758 | Reply :: term(). 759 | %% ==================================================================== 760 | set_options(ServerName, Options) -> 761 | csi_utils:call_server(ServerName, 762 | {'$set_options', Options}, 763 | ?DEFAULT_SERVICE_RETRY, 764 | ?DEFAULT_SERVICE_SLEEP, 765 | ?DEFAULT_CLIENT_TIMEOUT). 766 | 767 | 768 | % Test functions 769 | list_macros() -> 770 | ?LOGFORMAT(info, "CSI_SERVICE_NAME:~p~n" 771 | "CSI_SERVICE_MODULE:~p~n" 772 | "CSI_SERVER_MODULE:~p~n" 773 | "CSI_SERVICE_PROCESS_GROUP_NAME:~p~n" 774 | "DEFAULT_SERVICE_RETRY:~p~n" 775 | "DEFAULT_SERVICE_SLEEP:~p~n" 776 | "DEFAULT_CLIENT_TIMEOUT:~p~n" 777 | "DEFAULT_SERVER_TIMEOUT:~p~n" 778 | "LOGTYPE:~p~n", 779 | [?CSI_SERVICE_NAME, 780 | ?CSI_SERVICE_MODULE, 781 | ?CSI_SERVER_MODULE, 782 | ?CSI_SERVICE_PROCESS_GROUP_NAME, 783 | ?DEFAULT_SERVICE_RETRY, 784 | ?DEFAULT_SERVICE_SLEEP, 785 | ?DEFAULT_CLIENT_TIMEOUT, 786 | ?DEFAULT_SERVER_TIMEOUT, 787 | ?LOGTYPE 788 | ] 789 | ). 790 | 791 | process_foo_call(Args) -> 792 | csi:call(?CSI_SERVICE_NAME, 793 | process_foo, 794 | Args). 795 | process_too_long_call(Args) -> 796 | csi:call(?CSI_SERVICE_NAME, 797 | process_too_long, 798 | Args, 799 | 4000). 800 | process_crashing_call(Args) -> 801 | csi:call(?CSI_SERVICE_NAME, 802 | process_crashing, 803 | Args). 804 | process_foo_call_p(Args) -> 805 | csi:call_p(?CSI_SERVICE_NAME, 806 | process_foo, 807 | Args). 808 | process_too_long_call_p(Args) -> 809 | csi:call_p(?CSI_SERVICE_NAME, 810 | process_too_long, 811 | Args, 812 | 4000). 813 | process_crashing_call_p(Args) -> 814 | csi:call_p(?CSI_SERVICE_NAME, 815 | process_crashing, 816 | Args). 817 | process_foo_call_s(Args) -> 818 | csi:call_s(?CSI_SERVICE_NAME, 819 | process_foo, 820 | Args). 821 | process_too_long_call_s(Args) -> 822 | csi:call_s(?CSI_SERVICE_NAME, 823 | process_too_long, 824 | Args, 825 | 4000). 826 | process_crashing_call_s(Args) -> 827 | csi:call_s(?CSI_SERVICE_NAME, 828 | process_crashing, 829 | Args). 830 | process_foo_post_p(Args) -> 831 | csi:post_p(?CSI_SERVICE_NAME, 832 | process_foo, 833 | Args). 834 | process_too_long_post_p(Args) -> 835 | csi:post_p(?CSI_SERVICE_NAME, 836 | process_too_long, 837 | Args, 838 | 4000). 839 | process_crashing_post_p(Args) -> 840 | csi:post_p(?CSI_SERVICE_NAME, 841 | process_crashing, 842 | Args). 843 | process_foo_cast(Args) -> 844 | csi:cast_p( ?CSI_SERVICE_NAME, 845 | process_foo, Args). 846 | process_too_long_cast(Args) -> 847 | csi:cast_p(?CSI_SERVICE_NAME, 848 | process_too_long, 849 | Args, 850 | 4000). 851 | process_crashing_cast(Args) -> 852 | csi:cast_p(?CSI_SERVICE_NAME, 853 | process_crashing, 854 | Args). 855 | -------------------------------------------------------------------------------- /src/csi_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -module(csi_app). 11 | -behaviour(application). 12 | 13 | -export([start/2]). 14 | -export([stop/1]). 15 | -export([debug/0]). 16 | 17 | -include("csi_common.hrl"). 18 | 19 | debug() -> 20 | ok = application:start(compiler), 21 | ok = application:start(syntax_tools), 22 | ok = application:start(goldrush), 23 | ok = application:start(lager), 24 | ok = application:start(csi), 25 | ?LOGMSG(debug, "Application csi started"). 26 | 27 | start(_Type, _Args) -> 28 | csi_sup:start_link(). 29 | 30 | stop(_State) -> 31 | ok. 32 | -------------------------------------------------------------------------------- /src/csi_common.hrl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -define(CSI_SERVICE_MODULE,csi_service). 11 | -define(CSI_SERVER_MODULE,csi_server). 12 | -define(CSI_SERVICE_PROCESS_GROUP_NAME,csi_service_group). 13 | 14 | -ifdef(debug). 15 | -define(DEFAULT_CLIENT_TIMEOUT,infinity). 16 | -define(DEFAULT_SERVER_TIMEOUT,infinity). 17 | -else. 18 | -define(DEFAULT_CLIENT_TIMEOUT,60000). 19 | -define(DEFAULT_SERVER_TIMEOUT,55000). 20 | -endif. 21 | 22 | -define(DEFAULT_SERVICE_RETRY,2). 23 | -define(DEFAULT_SERVICE_SLEEP,200). 24 | -define(CSI_DEFAULT_OPTIONS,[{server_timeout, ?DEFAULT_SERVER_TIMEOUT}]). 25 | 26 | -type(property_list() :: list(proplists:property())). 27 | 28 | -ifndef(LOGFORMAT). 29 | 30 | -ifdef(lager). 31 | -compile([{parse_transform, lager_transform}]). 32 | -define(LOGTYPE,"lager"). 33 | -define(LOGFORMAT(Level,Format,Args), 34 | ok = lager:Level(Format,Args)). 35 | -define(LOGMSG(Level,Format), 36 | ok = lager:Level(Format)). 37 | 38 | -else. 39 | -define(LOGTYPE,"io:format"). 40 | -define(LOGFORMAT(Level,Format,Args), 41 | ok = io:format("~p: ~s",[Level,io_lib:format(Format, Args)])). 42 | -define(LOGMSG(Level,Format), 43 | ok = io:format("~p: ~p",[Level,Format])). 44 | -endif. 45 | -endif. -------------------------------------------------------------------------------- /src/csi_server.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -module(csi_server). 11 | 12 | -behaviour(gen_server). 13 | 14 | -include("csi_common.hrl"). 15 | -include("csi.hrl"). 16 | 17 | -export([init/1, 18 | handle_call/3, 19 | handle_cast/2, 20 | handle_info/2, 21 | terminate/2, 22 | code_change/3]). 23 | 24 | -callback init_service(InitArgs :: term()) -> Result :: term(). 25 | -callback init(InitArgs :: term(), State :: term()) -> Result :: term(). 26 | -callback terminate(Reason :: term(), State :: term()) -> Result :: term(). 27 | -callback terminate_service(Reason :: term(), 28 | State :: term()) -> Result :: term(). 29 | 30 | %% ==================================================================== 31 | %% API functions 32 | %% ==================================================================== 33 | 34 | -export([process_service_request/8 35 | ]). 36 | 37 | -record(csi_service_state, {service_name, 38 | service_module, 39 | service_state, 40 | stats_collect = true, 41 | stats_table, 42 | stats_temp_table, 43 | stats_process_table, 44 | stats_module = ?DEFAULT_STATS_MODULE, 45 | stats_requests_include = [all], 46 | stats_requests_exclude = [], 47 | stats_types = [], 48 | server_timeout = ?DEFAULT_SERVER_TIMEOUT 49 | }). 50 | 51 | -type csi_service_state() :: #csi_service_state{}. 52 | 53 | -export_type([csi_service_state/0]). 54 | 55 | %% init/1 56 | %% ==================================================================== 57 | %% @doc 58 | %% gen_server:init/1 59 | %% @end 60 | -spec init({Name :: atom(), 61 | Module :: atom(), 62 | InitArgs :: term(), 63 | Options :: property_list()}) -> 64 | Result when 65 | Result :: {ok, State} 66 | | {stop, Reason :: term()}, 67 | State :: term(). 68 | %% ==================================================================== 69 | init({Name, Module, InitArgs, Options}) -> 70 | process_flag(trap_exit, true), 71 | StatTable = ets:new(Name, [private]), 72 | StatTempTable = ets:new(list_to_atom(atom_to_list(Name) ++ "_temp"), 73 | [private]), 74 | ProcTable = ets:new(list_to_atom(atom_to_list(Name) ++ "_procs"), 75 | [private]), 76 | pg2:create(?CSI_SERVICE_PROCESS_GROUP_NAME), 77 | ok = csi:register(), 78 | try case Module:init_service(InitArgs) of 79 | {ok, SState} -> 80 | StatsInit = ?DEFAULT_STATS_MODULE:init_stats(), 81 | {ok, #csi_service_state{service_name = Name, 82 | service_module = Module, 83 | service_state = SState, 84 | stats_types = StatsInit, 85 | stats_table = StatTable, 86 | stats_temp_table = StatTempTable, 87 | stats_process_table = ProcTable, 88 | server_timeout = 89 | proplists:get_value( 90 | server_timeout, 91 | Options, 92 | ?DEFAULT_SERVER_TIMEOUT)}}; 93 | WAFIT -> 94 | {stop, WAFIT} 95 | end 96 | catch 97 | A:B -> 98 | log_error(A, B, erlang:get_stacktrace(), Module, init_service, 99 | InitArgs), 100 | {stop, exception} 101 | end. 102 | 103 | %% handle_call/3 104 | %% ==================================================================== 105 | %% @doc gen_server:handle_call/3 107 | %% @end 108 | -spec handle_call(Request :: term(), 109 | From :: {pid(), Tag :: term()}, 110 | State :: term()) -> 111 | Result when 112 | Result :: {reply, Reply, NewState} 113 | | {reply, Reply, NewState, Timeout} 114 | | {reply, Reply, NewState, hibernate} 115 | | {noreply, NewState} 116 | | {noreply, NewState, Timeout} 117 | | {noreply, NewState, hibernate} 118 | | {stop, Reason, Reply, NewState} 119 | | {stop, Reason, NewState}, 120 | Reply :: term(), 121 | NewState :: term(), 122 | Timeout :: non_neg_integer() | infinity, 123 | Reason :: term(). 124 | %% ==================================================================== 125 | 126 | 127 | handle_call(stop, _From, State) -> 128 | {stop, normal, ok, State}; 129 | 130 | handle_call({'$set_options', Options}, _From, State) -> 131 | NewState = 132 | lists:foldl( 133 | fun({Param, Value}, AccIn) -> 134 | case Param of 135 | server_timeout -> 136 | AccIn#csi_service_state{server_timeout = Value}; 137 | _ -> 138 | AccIn 139 | end 140 | end, State, Options), 141 | {reply, NewState, NewState}; 142 | 143 | handle_call('$collect_services_status', _From, State) -> 144 | {reply, csi_service:collect_services_status(State), State}; 145 | 146 | handle_call('$service_status', _From, State) -> 147 | {reply, State, State}; 148 | 149 | handle_call('$stats_start_all', From, State) -> 150 | csi_service:stats_start_all(), 151 | handle_call('$stats_start', From, State); 152 | 153 | handle_call('$stats_start', _From, State) -> 154 | NewState = State#csi_service_state{stats_collect = true}, 155 | {reply, ok, NewState}; 156 | 157 | handle_call('$stats_stop', _From, State) -> 158 | NewState = State#csi_service_state{stats_collect = false}, 159 | {reply, ok, NewState}; 160 | 161 | handle_call('$stats_stop_all', From, State) -> 162 | csi_service:stats_stop_all(), 163 | handle_call('$stats_stop', From, State); 164 | 165 | handle_call({'$stats_include_funs', FunctionList}, From, State) 166 | when is_atom(FunctionList) -> 167 | handle_call({'$stats_include_funs', [FunctionList]}, From, State); 168 | 169 | handle_call({'$stats_include_funs', FunctionList}, _From, State) -> 170 | % first remove the functions from the exclude list 171 | ExcludeList = 172 | csi_utils:remove_elems_from_list( 173 | FunctionList, 174 | State#csi_service_state.stats_requests_exclude), 175 | % second include it in stats requests if it does not contain all 176 | NewState = 177 | case lists:member(all, 178 | State#csi_service_state.stats_requests_include) of 179 | true -> 180 | State#csi_service_state{stats_requests_exclude = ExcludeList}; 181 | _ -> 182 | % add to the list of requests if it is not there 183 | IncludeList = 184 | csi_utils:add_elems_to_list( 185 | FunctionList, 186 | State#csi_service_state.stats_requests_include), 187 | State#csi_service_state{stats_requests_exclude = ExcludeList, 188 | stats_requests_include = IncludeList} 189 | end, 190 | {reply, ok, NewState}; 191 | 192 | handle_call({'$stats_exclude_funs', FunctionList}, From, State) 193 | when is_atom(FunctionList) -> 194 | handle_call({'$stats_exclude_funs', [FunctionList]}, From, State); 195 | 196 | handle_call({'$stats_exclude_funs', FunctionList}, _From, State) -> 197 | % first, remove it from the requests 198 | IncludeList = 199 | csi_utils:remove_elems_from_list( 200 | FunctionList, 201 | State#csi_service_state.stats_requests_include), 202 | % second, add it to the exclude list 203 | ExcludeList = csi_utils:add_elems_to_list( 204 | FunctionList, 205 | State#csi_service_state.stats_requests_exclude), 206 | NewState = State#csi_service_state{stats_requests_exclude = ExcludeList, 207 | stats_requests_include = IncludeList}, 208 | {reply, ok, NewState}; 209 | 210 | handle_call({'$stats_set_funs', FunctionList}, From, State) 211 | when is_atom(FunctionList) -> 212 | handle_call({'$stats_set_funs', [FunctionList]}, From, State); 213 | 214 | handle_call({'$stats_set_funs', FunctionList}, _From, State) -> 215 | NewState = State#csi_service_state{stats_requests_include = FunctionList, 216 | stats_requests_exclude = []}, 217 | {reply, ok, NewState}; 218 | 219 | handle_call('$stats_get_all', _From, State) -> 220 | %% Reply = lists:flatten(ets:foldl(fun (X, AccIn) -> 221 | %% [io_lib:format("~p~n", [X])|AccIn] 222 | %% end, [], State#rstate.stat_table)), 223 | Reply = ets:tab2list(State#csi_service_state.stats_table), 224 | {reply, Reply, State}; 225 | 226 | handle_call({'$stats_get_funs', FunctionList}, From, State) 227 | when is_atom(FunctionList) -> 228 | handle_call({'$stats_get_funs', [FunctionList]}, From, State); 229 | 230 | handle_call({'$stats_get_funs', _FunctionList}, _From, State) -> 231 | %% @TODO return the stats for only the functions in the Functionlist 232 | Reply = ets:tab2list(State#csi_service_state.stats_table), 233 | {reply, Reply, State}; 234 | 235 | handle_call({'$stats_get_types', TypeList}, From, State) 236 | when is_atom(TypeList) -> 237 | handle_call({'$stats_get_types', [TypeList]}, From, State); 238 | 239 | handle_call({'$stats_get_types', _TypeList}, _From, State) -> 240 | %% @TODO return the stats for only the types in the Typelist 241 | Reply = ets:tab2list(State#csi_service_state.stats_table), 242 | {reply, Reply, State}; 243 | 244 | handle_call({'$stats_get_specific', _Function, _Type}, _From, State) -> 245 | %% @TODO return the stats for only the function for a type 246 | Reply = ets:tab2list(State#csi_service_state.stats_table), 247 | {reply, Reply, State}; 248 | 249 | handle_call('$stats_get_process_table', _From, State) -> 250 | Reply = ets:tab2list(State#csi_service_state.stats_process_table), 251 | {reply, Reply, State}; 252 | 253 | handle_call({'$stats_include_type', TypeList}, From, State) 254 | when is_atom(TypeList) -> 255 | handle_call({'$stats_include_type', [TypeList]}, From, State); 256 | 257 | handle_call({'$stats_include_type', TypeList}, _From, State) -> 258 | IncludeList = csi_utils:add_elems_to_list( 259 | TypeList, 260 | State#csi_service_state.stats_types), 261 | NewState = State#csi_service_state{stats_types = IncludeList}, 262 | {reply, ok, NewState}; 263 | 264 | handle_call({'$stats_exclude_type', TypeList}, From, State) 265 | when is_atom(TypeList) -> 266 | handle_call({'$stats_exclude_type', [TypeList]}, From, State); 267 | 268 | handle_call({'$stats_exclude_type', TypeList}, _From, State) -> 269 | % first, remove it from the requests 270 | ExcludeList = csi_utils:remove_elems_from_list( 271 | TypeList, 272 | State#csi_service_state.stats_types), 273 | NewState = State#csi_service_state{stats_types = ExcludeList}, 274 | {reply, ok, NewState}; 275 | 276 | handle_call({'$stats_change_module', Module}, _From, State) -> 277 | %% @TODO check if the module is loaded and load it if not 278 | 279 | NewState = State#csi_service_state{stats_module = Module}, 280 | {reply, ok, NewState}; 281 | 282 | handle_call({'$stats_params'}, _From, State) -> 283 | {reply, State#csi_service_state.stats_types, State}; 284 | 285 | handle_call({'$stats_params', Type}, _From, State) -> 286 | {reply, proplists:get_value(Type, 287 | State#csi_service_state.stats_types), 288 | State}; 289 | 290 | handle_call({'$stats_param_get', Type, Parameter}, _From, State) -> 291 | {reply, 292 | proplists:get_value( 293 | Parameter, 294 | proplists:get_value(Type, State#csi_service_state.stats_types)), 295 | State}; 296 | 297 | handle_call({'$stats_param_set', Type, Parameter, Value}, _From, State) -> 298 | NewValues = 299 | lists:keyreplace(Parameter, 300 | 1, 301 | proplists:get_value( 302 | Type, 303 | State#csi_service_state.stats_types), 304 | UpdatedValue = 305 | {Parameter, Value}), 306 | NewStats = lists:keyreplace(Type, 307 | 1, 308 | State#csi_service_state.stats_types, 309 | {Type, NewValues}), 310 | {reply, UpdatedValue, State#csi_service_state{stats_types = NewStats}}; 311 | 312 | handle_call({call_p, Request, Args, TimeoutForProcessing} = R, From, State) -> 313 | collect_stats(start, State, Request, R, Ref = make_ref()), 314 | Pid = proc_lib:spawn_link(?MODULE, 315 | process_service_request, 316 | [From, State#csi_service_state.service_module, 317 | Request, Args, State, 318 | find_timeout(TimeoutForProcessing, State), 319 | self(), true]), 320 | ets:insert(State#csi_service_state.stats_process_table, {Pid, 321 | Ref, 322 | Request, 323 | R}), 324 | {noreply, State}; 325 | 326 | handle_call({call_s, Request, Args} = R, _From, State) -> 327 | collect_stats(start, State, Request, R, Ref = make_ref()), 328 | Module = State#csi_service_state.service_module, 329 | try case Module:init(Args, State#csi_service_state.service_state) of 330 | {ok, PState} -> 331 | {Reply, EState} = Module:Request(Args, PState), 332 | Module:terminate(normal, EState), 333 | collect_stats(stop, State, Request, R, Ref), 334 | {reply, Reply, State}; 335 | WAFIT -> 336 | collect_stats(clean, State, Request, R, Ref), 337 | {reply, WAFIT, State} 338 | end 339 | catch 340 | A:B -> 341 | log_error(A, B, erlang:get_stacktrace(), 342 | Module, Request, 343 | "~p, ~p", [Args, State#csi_service_state.service_state]), 344 | collect_stats(clean, State, Request, R, Ref), 345 | {reply, {error, exception}, State} 346 | end; 347 | 348 | handle_call({post_p, Request, Args, TimeoutForProcessing} = R, From, State) -> 349 | collect_stats(start, State, Request, R, Ref = make_ref()), 350 | Pid = proc_lib:spawn_link(?MODULE, 351 | process_service_request, 352 | [From, State#csi_service_state.service_module, 353 | Request, Args, State, 354 | find_timeout(TimeoutForProcessing, State), 355 | self(), true]), 356 | ets:insert(State#csi_service_state.stats_process_table, 357 | {Pid, Ref, Request, R}), 358 | {reply, {posted, Pid, From}, State}; 359 | 360 | handle_call({cast_p, Request, Args, TimeoutForProcessing} = R, From, State) -> 361 | collect_stats(start, State, Request, R, Ref = make_ref()), 362 | Pid = proc_lib:spawn_link(?MODULE, 363 | process_service_request, 364 | [From, State#csi_service_state.service_module, 365 | Request, Args, State, 366 | find_timeout(TimeoutForProcessing, State), 367 | self(), false]), 368 | ets:insert(State#csi_service_state.stats_process_table, 369 | {Pid, Ref, Request, R}), 370 | {reply, {casted, Pid} , State}; 371 | 372 | handle_call({Request, Args}, From, State) -> 373 | collect_stats(start, State, Request, Request, Ref = make_ref()), 374 | Module = State#csi_service_state.service_module, 375 | try 376 | ReturnValue = Module:handle_call({Request, Args}, 377 | From, 378 | State#csi_service_state.service_state), 379 | collect_stats(stop, State, Request, Request, Ref), 380 | case ReturnValue of 381 | {reply, Reply, NewState} -> 382 | {reply, Reply, 383 | State#csi_service_state{service_state = NewState}}; 384 | {reply, Reply, NewState, Timeout} -> 385 | {reply, Reply, 386 | State#csi_service_state{service_state = NewState}, Timeout}; 387 | {noreply, NewState} -> 388 | {noreply, State#csi_service_state{service_state = NewState}}; 389 | {noreply, NewState, Timeout} -> 390 | {noreply, 391 | State#csi_service_state{service_state = NewState}, Timeout}; 392 | {stop, Reason, Reply, NewState} -> 393 | {stop, Reason, Reply, 394 | State#csi_service_state{service_state = NewState}}; 395 | {stop, Reason, NewState} -> 396 | {stop, Reason, 397 | State#csi_service_state{service_state = NewState}} 398 | end 399 | catch 400 | A:B -> 401 | log_error(A, B, erlang:get_stacktrace(), 402 | Module, handle_call, "~p, ~p", 403 | [Request, State#csi_service_state.service_state]), 404 | collect_stats(clean, State, Request, Request, Ref), 405 | {reply, {error, exception}, State} 406 | end. 407 | 408 | process_service_request(From, Module, Request, Args, State, 409 | TimeoutForProcessing, Parent, NeedReply) -> 410 | TRef = case TimeoutForProcessing of 411 | infinity -> 412 | undefined; 413 | RealTimeout -> 414 | KillMessage = case NeedReply of 415 | true -> 416 | {kill_worker_reply, 417 | self(), 418 | From, 419 | Module, 420 | Request, 421 | Args}; 422 | false -> 423 | {kill_worker_noreply, 424 | self(), 425 | Module, 426 | Request, 427 | Args} 428 | end, 429 | erlang:send_after(RealTimeout, 430 | Parent, 431 | KillMessage) 432 | end, 433 | try case Module:init(Args, State#csi_service_state.service_state) of 434 | {ok, PState} -> 435 | {Reply, EState} = Module:Request(Args, PState), 436 | case NeedReply of 437 | true -> 438 | gen_server:reply(From, Reply); 439 | _ -> 440 | ok 441 | end, 442 | Module:terminate(normal, EState); 443 | WAFIT -> 444 | gen_server:reply(From, WAFIT) 445 | end 446 | catch 447 | A:B -> 448 | log_error(A, B, erlang:get_stacktrace(), Module, Request, Args), 449 | catch gen_server:reply(From, {error, exception}), 450 | _ = case TRef of 451 | undefined -> 452 | ok; 453 | RealTRef0 -> 454 | erlang:cancel_timer(RealTRef0) 455 | end, 456 | erlang:exit(exception) 457 | end, 458 | case TRef of 459 | undefined -> 460 | ok; 461 | RealTRef -> 462 | erlang:cancel_timer(RealTRef) 463 | end. 464 | 465 | %% handle_cast/2 466 | %% ==================================================================== 467 | %% @doc gen_server:handle_cast/2 469 | %% @end 470 | -spec handle_cast(Request :: term(), State :: term()) -> Result when 471 | Result :: {noreply, NewState}, 472 | NewState :: term(). 473 | %% ==================================================================== 474 | handle_cast({cast, FunctionRequest, Args}, State) -> 475 | _ = handle_call({FunctionRequest, Args}, {self(), make_ref()}, State), 476 | {noreply, State}; 477 | 478 | 479 | handle_cast(_Msg, State) -> 480 | {noreply, State}. 481 | 482 | 483 | %% handle_info/2 484 | %% ==================================================================== 485 | %% @doc gen_server:handle_info/2 487 | %% @end 488 | -spec handle_info(Info :: timeout | term(), State :: term()) -> Result when 489 | Result :: {noreply, NewState} 490 | | {noreply, NewState, Timeout} 491 | | {noreply, NewState, hibernate} 492 | | {stop, Reason :: term(), NewState}, 493 | NewState :: term(), 494 | Timeout :: non_neg_integer() | infinity. 495 | %% ==================================================================== 496 | handle_info(Info, State) -> 497 | case try (State#csi_service_state.service_module): 498 | handle_info(Info, State#csi_service_state.service_state) 499 | catch 500 | _:_ -> 501 | continue 502 | end of 503 | continue -> 504 | case Info of 505 | {'EXIT', Pid, Reason} -> 506 | case ets:lookup( 507 | State#csi_service_state.stats_process_table, Pid) of 508 | [{Pid, Ref, Request, R}] -> 509 | case Reason =:= normal of 510 | true -> 511 | collect_stats(stop, State, Request, R, Ref); 512 | _ -> 513 | collect_stats(clean, 514 | State, 515 | undefined, 516 | undefined, 517 | Ref) 518 | end, 519 | ets:delete( 520 | State#csi_service_state.stats_process_table, Pid); 521 | WAFIT -> 522 | ?LOGFORMAT(warning, 523 | "Pid ~p not returned value ~p" 524 | " from process table~n", 525 | [Pid, WAFIT]) 526 | end; 527 | {kill_worker_reply, Pid, CallerPid, Module, Request, Args} -> 528 | catch gen_server:reply(CallerPid, {error, timeout_killed}), 529 | ?LOGFORMAT(warning, 530 | "Worker killed with reply. Pid:~p. " 531 | "Called function was: ~p:~p(~p)~n", 532 | [Pid, Module, Request, Args]), 533 | erlang:exit(Pid, kill); 534 | {kill_worker_noreply, Pid, Module, Request, Args} -> 535 | ?LOGFORMAT(warning, "Worker killed with no reply Pid:~p. " 536 | "Called function was: ~p:~p(~p)~n", 537 | [Pid, Module, Request, Args]), 538 | erlang:exit(Pid, kill); 539 | WAFIT -> 540 | ?LOGFORMAT(warning, 541 | "Unhandled message received for service ~p." 542 | "Module to be called:~p. Message:~p", 543 | [State#csi_service_state.service_name, 544 | State#csi_service_state.service_module, 545 | WAFIT]) 546 | end, 547 | {noreply, State}; 548 | {noreply, NewState} -> 549 | {noreply, State#csi_service_state{service_state = NewState}}; 550 | {noreply, NewState, Timeout} -> 551 | {noreply, State#csi_service_state{service_state = NewState}, 552 | Timeout}; 553 | {stop, Reason, NewState} -> 554 | {stop, Reason, State#csi_service_state{service_state = NewState}} 555 | end. 556 | 557 | %% terminate/2 558 | %% ==================================================================== 559 | %% @doc gen_server:terminate/2 561 | %% @end 562 | -spec terminate(Reason, State :: #csi_service_state{}) -> Any :: any() when 563 | Reason :: normal 564 | | shutdown 565 | | {shutdown, term()} 566 | | term(). 567 | %% ==================================================================== 568 | terminate(Reason, #csi_service_state{service_module = Module, 569 | service_name = _Name, 570 | service_state = ServiceState, 571 | stats_table = StatTable, 572 | stats_temp_table = TempStatTable, 573 | stats_process_table = ProcessTable}) -> 574 | ets:delete(StatTable), 575 | ets:delete(ProcessTable), 576 | ets:delete(TempStatTable), 577 | try Module:terminate_service(Reason, ServiceState) 578 | catch 579 | A:B -> 580 | log_error(A, B, erlang:get_stacktrace(), 581 | Module, terminate_service, 582 | "~p, ~p", [Reason, ServiceState]), 583 | {error, exception} 584 | end. 585 | 586 | 587 | %% code_change/3 588 | %% ==================================================================== 589 | %% @doc gen_server:code_change/3 591 | %% @end 592 | -spec code_change(OldVsn, State :: term(), Extra :: term()) -> Result when 593 | Result :: {ok, NewState :: term()}, 594 | OldVsn :: Vsn | {down, Vsn}, 595 | Vsn :: term(). 596 | %% ==================================================================== 597 | code_change(_OldVsn, State, _Extra) -> 598 | {ok, State}. 599 | 600 | collect_stats(Stage, State, Request, R, Ref) -> 601 | case State#csi_service_state.stats_collect of 602 | true -> 603 | TimeStamp = csi_utils:now_usec(), 604 | case stats_to_collect(Request, State) of 605 | true -> 606 | try 607 | lists:foreach( 608 | fun ({M, Params}) -> 609 | (State#csi_service_state.stats_module): 610 | M(Stage, 611 | Request, 612 | R, 613 | Ref, 614 | Params, 615 | State#csi_service_state.stats_table, 616 | State#csi_service_state.stats_temp_table, 617 | TimeStamp 618 | ) 619 | end, 620 | State#csi_service_state.stats_types) 621 | catch 622 | A:B -> 623 | log_error(A, B, erlang:get_stacktrace(), 624 | State#csi_service_state.stats_module, 625 | State#csi_service_state.stats_types, 626 | "~p, ~p, ~p, ~p, ~p, ~p, ~p, ~p", 627 | [Stage, Request, R, Ref, params, 628 | State#csi_service_state.stats_table, 629 | State#csi_service_state.stats_temp_table, 630 | TimeStamp]) 631 | end; 632 | _ -> 633 | ok 634 | end; 635 | _ -> 636 | ok 637 | end. 638 | 639 | stats_to_collect(Request, State) -> 640 | case lists:member(all, State#csi_service_state.stats_requests_include) of 641 | true -> 642 | not lists:member(Request, 643 | State#csi_service_state.stats_requests_exclude); 644 | _ -> 645 | lists:member(Request, 646 | State#csi_service_state.stats_requests_include) and 647 | not lists:member(Request, 648 | State#csi_service_state.stats_requests_exclude) 649 | end. 650 | 651 | find_timeout(TimeOut, State) -> 652 | case TimeOut of 653 | undefined -> 654 | State#csi_service_state.server_timeout; 655 | default -> 656 | ?DEFAULT_SERVER_TIMEOUT; 657 | _ -> 658 | TimeOut 659 | end. 660 | 661 | log_error(A, B, Stack, Module, Request, Arg) -> 662 | log_error(A, B, Stack, Module, Request, "~p", [Arg]). 663 | 664 | 665 | log_error(A, B, Stack, Module, Request, ArgFormat, Args) -> 666 | ?LOGFORMAT(error, 667 | "Exception ~p:~p. Stack:~p. When calling ~p:~p(~s).", 668 | [A, 669 | B, 670 | Stack, 671 | Module, 672 | Request, 673 | lists:flatten(io_lib:format(ArgFormat, Args))]). 674 | -------------------------------------------------------------------------------- /src/csi_service.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -module(csi_service). 11 | -behaviour(csi_server). 12 | -include("csi_common.hrl"). 13 | -include("csi.hrl"). 14 | 15 | %% ==================================================================== 16 | %% API functions 17 | %% ==================================================================== 18 | -export([init_service/1, 19 | init/2, 20 | terminate/2, 21 | terminate_service/2, 22 | handle_call/3]). 23 | 24 | -record(csi_service_state, {}). 25 | -record(csi_request_state, {}). 26 | 27 | -export([stats_start_all/0, 28 | stats_stop_all/0, 29 | services/2, 30 | start_services/2 31 | ]). 32 | 33 | -export([collect_services_status/1 34 | ]). 35 | 36 | -export([process_foo/2, 37 | process_too_long/2, 38 | process_crashing/2]). 39 | 40 | % init the global service 41 | init_service(_InitArgs) -> 42 | csi:cast(?CSI_SERVICE_NAME, start_services, []), 43 | {ok, #csi_service_state{}}. 44 | 45 | % init paralell process 46 | init(_Args, _ServiceState = #csi_service_state{}) -> 47 | {ok, #csi_request_state{}}. 48 | 49 | % terminate parallell process 50 | terminate(Reason, _State) -> 51 | case Reason of 52 | normal -> 53 | ok; 54 | WAFIT -> 55 | ?LOGFORMAT(info, 56 | "Common Service Interface process terminated " 57 | "for reason ~p", [WAFIT]) 58 | end. 59 | 60 | terminate_service(_Reason, _State) -> 61 | normal. 62 | 63 | start_services(_Args, State) -> 64 | ServerList = 65 | case application:get_env(?CSI_APPLICATION_NAME, servers) of 66 | {ok, Value} -> 67 | Value; 68 | undefined -> 69 | [] 70 | end, 71 | {start_servers(ServerList), State}. 72 | 73 | services(_Args, State) -> 74 | {[erlang:process_info(X, registered_name) || 75 | X <- pg2:get_members(?CSI_SERVICE_PROCESS_GROUP_NAME)], 76 | State}. 77 | 78 | handle_call({Request, Args}, _From, State) -> 79 | ?LOGFORMAT(info, 80 | "Calling service through handle_call(~p)", 81 | [{Request, Args}]), 82 | {Reply, NewState} = ?MODULE:Request(Args, State), 83 | {reply, Reply, NewState}; 84 | 85 | handle_call(Request, _From, State) -> 86 | ?LOGFORMAT(warning, "Unhandled request:~p for csi_service with state:~p~n", 87 | [Request, State]), 88 | {reply, ok, State}. 89 | 90 | process_foo(_Args, State) -> 91 | {hello_world, State}. 92 | 93 | process_too_long(_Args, State) -> 94 | timer:sleep(5000), 95 | {long_job_finished, State}. 96 | 97 | process_crashing(Args, State) -> 98 | A = Args - Args, 99 | {A, State}. 100 | 101 | %% ==================================================================== 102 | %% Internal functions 103 | %% ==================================================================== 104 | stats_start_all() -> 105 | lists:foreach(fun(Server) -> 106 | case Server =:= self() of 107 | true -> 108 | ok; 109 | _ -> 110 | csi:stats_start(Server) 111 | end 112 | end, 113 | pg2:get_members(?CSI_SERVICE_PROCESS_GROUP_NAME)). 114 | 115 | stats_stop_all() -> 116 | lists:foreach(fun(Server) -> 117 | case Server =:= self() of 118 | true -> 119 | ok; 120 | _ -> 121 | csi:stats_stop(Server) 122 | end 123 | end, 124 | pg2:get_members(?CSI_SERVICE_PROCESS_GROUP_NAME)). 125 | 126 | collect_services_status(OwnState) -> 127 | lists:foldl(fun(Server, Acc) -> 128 | Status = case Server =:= self() of 129 | true -> 130 | OwnState; 131 | _ -> 132 | csi:service_status(Server) 133 | end, 134 | [{erlang:process_info(Server, registered_name), Status} 135 | | Acc] 136 | end, 137 | [], 138 | pg2:get_members(?CSI_SERVICE_PROCESS_GROUP_NAME)). 139 | 140 | start_servers([]) -> 141 | ok; 142 | 143 | start_servers([{Name, Module, InitArgs, ChildSpec} | Tail]) -> 144 | Child = case ChildSpec of 145 | default -> 146 | #{id => Name, 147 | start => {?CSI_APPLICATION_NAME, 148 | start_link, 149 | [Name, Module, InitArgs]}, 150 | restart => permanent, 151 | shutdown => 2000, 152 | type => worker, 153 | modules => []}; 154 | _ -> 155 | ChildSpec 156 | end, 157 | case supervisor:start_child(csi_sup, Child) of 158 | {ok, _WorkerPId} -> 159 | ?LOGFORMAT(info, "Service ~p started by CSI.", [Name]); 160 | WAFIT -> 161 | ?LOGFORMAT(error, 162 | "CSI could not start service:~p. Reason:~p", 163 | [Name, WAFIT]) 164 | end, 165 | start_servers(Tail). 166 | -------------------------------------------------------------------------------- /src/csi_stats.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -module(csi_stats). 11 | 12 | %% ==================================================================== 13 | %% API functions 14 | %% ==================================================================== 15 | -export([init_stats/0, 16 | response_time/8, 17 | req_per_sec/8 18 | ]). 19 | 20 | init_stats() -> 21 | [{response_time, [{"last_nth_to_collect", 50}, 22 | {"normalize_to_nth", 40}]}, 23 | {req_per_sec, [{"time_window", 50} 24 | ]} 25 | ]. 26 | response_time(start, _Request, _R, Ref, _Params, _Tab, TempTab, TimeStamp) -> 27 | ets:insert(TempTab, {{response_time_first, Ref}, TimeStamp}); 28 | 29 | response_time(stop, Request, _R, Ref, Params, Tab, TempTab, TimeStamp) -> 30 | case ets:lookup(TempTab, {response_time_first, Ref}) of 31 | [{{response_time_first, Ref}, Value}] -> 32 | ResponseTime = TimeStamp - Value, 33 | ets:delete(TempTab, {response_time_first, Ref}), 34 | {NrOfReqs, ART, _Avg, MinRt, MaxRt} = 35 | case ets:lookup(Tab, {response_time, Request}) of 36 | [] -> 37 | {0, 0, 0, 10000000000000, 0}; 38 | [{{response_time, Request}, Values}] -> 39 | Values 40 | end, 41 | {NormalizedART, NewNr} = 42 | case proplists:get_value("last_nth_to_collect", 43 | Params, 44 | 50) =:= NrOfReqs of 45 | true -> 46 | NormalizeToNth = proplists:get_value("normalize_to_nth", 47 | Params, 48 | 40), 49 | {( ART / NrOfReqs ) * NormalizeToNth, 50 | NormalizeToNth + 1}; 51 | _ -> 52 | {ART, NrOfReqs + 1} 53 | end, 54 | AllRespTime = NormalizedART + ResponseTime, 55 | NewMinRt = min(MinRt, ResponseTime), 56 | NewMaxRt = max(MaxRt, ResponseTime), 57 | ets:insert(Tab, {{response_time, Request}, {NewNr, 58 | AllRespTime, 59 | AllRespTime/NewNr, 60 | NewMinRt, 61 | NewMaxRt}}) 62 | end; 63 | 64 | response_time(clean, _Request, _R, Ref, _Params, _Tab, TempTab, _TimeStamp) -> 65 | case Ref of 66 | undefined -> 67 | ok; 68 | Else -> 69 | ets:delete(TempTab, {response_time_first, Else}) 70 | end. 71 | 72 | req_per_sec(start, Request, _R, _Ref, Params, Tab, TempTab, TimeStamp) -> 73 | ReceivedTimestampList = 74 | case ets:lookup(TempTab, {req_per_sec_received_list, Request}) of 75 | [] -> 76 | []; 77 | [{{req_per_sec_received_list, Request}, Values}] -> 78 | Values 79 | end, 80 | RTL = 81 | case proplists:get_value("nr_of_timestamps", 82 | Params, 83 | 50) =< length(ReceivedTimestampList) of 84 | true -> 85 | [TimeStamp | lists:droplast(ReceivedTimestampList)]; 86 | _ -> 87 | [TimeStamp | ReceivedTimestampList] 88 | end, 89 | RPS = 90 | case length(RTL) >= 2 of 91 | true -> 92 | try 93 | 1000000 * length(RTL) / (TimeStamp - lists:last(RTL)) 94 | catch 95 | _:_ -> 96 | 99999999 97 | end; 98 | _ -> 99 | 0 100 | end, 101 | ets:insert(TempTab, {{req_per_sec_received_list, Request}, RTL}), 102 | ets:insert(Tab, {{req_per_sec, Request}, RPS}); 103 | 104 | req_per_sec(stop, _Request, _R, _Ref, _Params, _Tab, _TempTab, _TimeStamp) -> 105 | ok; 106 | req_per_sec(clean, _Request, _R, _Ref, _Params, _Tab, _TempTab, _TimeStamp) -> 107 | ok. 108 | 109 | %% ==================================================================== 110 | %% Internal functions 111 | %% ==================================================================== -------------------------------------------------------------------------------- /src/csi_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | -module(csi_sup). 11 | -behaviour(supervisor). 12 | 13 | -include("csi.hrl"). 14 | 15 | -export([start_link/0]). 16 | -export([init/1]). 17 | 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | init([]) -> 22 | CSIServer = {?CSI_SERVICE_NAME, {csi, start_link, []}, 23 | permanent, 2000, worker, [csi]}, 24 | Procs = [CSIServer], 25 | {ok, {{one_for_one, 3, 10}, Procs}}. 26 | -------------------------------------------------------------------------------- /src/csi_utils.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Zsolt Laky 3 | %%% @copyright (C) 2016, Erlang Solutions. 4 | %%% @doc 5 | %%% Common Service Interface application 6 | %%% @end 7 | %%% Created : 20 Jun 2015 by Erlang Solutions 8 | %%%------------------------------------------------------------------- 9 | 10 | 11 | -module(csi_utils). 12 | -include("csi_common.hrl"). 13 | 14 | %% ==================================================================== 15 | %% API functions 16 | %% ==================================================================== 17 | -export([watchdog_create/2, 18 | watchdog_loop/3]). 19 | 20 | 21 | -export([add_elems_to_list/2, 22 | remove_elems_from_list/2, 23 | now_usec/0, 24 | timestamp_to_usec/1 25 | ]). 26 | 27 | -export([call_server/2, 28 | call_server/3, 29 | call_server/4, 30 | call_server/5 31 | ]). 32 | 33 | now_usec() -> 34 | timestamp_to_usec(os:timestamp()). 35 | 36 | timestamp_to_usec({MegaSecs, Secs, MicroSecs}) -> 37 | (MegaSecs * 1000000 + Secs) * 1000000 + MicroSecs. 38 | 39 | add_elems_to_list(ElemList, List) -> 40 | lists:foldl(fun (Elem, AccIn) -> 41 | case lists:member(Elem, AccIn) of 42 | true -> 43 | AccIn; 44 | _ -> 45 | AccIn ++ [Elem] 46 | end 47 | end, 48 | List, 49 | ElemList). 50 | 51 | remove_elems_from_list(ElemList, List) -> 52 | lists:filter(fun (Elem) -> 53 | not lists:member(Elem, ElemList) 54 | end, 55 | List). 56 | 57 | %% watchdog_create/2 58 | %% @doc Spawns a process for watchdog_loop/3. 59 | %% Returns the pid of the created watchdog process. 60 | %% @end 61 | -spec watchdog_create(MessageToSendWhenTimeout :: term(), 62 | Timeout ::non_neg_integer) -> Result :: pid(). 63 | watchdog_create(Timeout, MessageToSendWhenTimeout) -> 64 | spawn(?MODULE, watchdog_loop, [self(), MessageToSendWhenTimeout, Timeout]). 65 | 66 | -spec watchdog_loop(SenderPid :: pid(), 67 | MessageToSendWhenTimeout :: term(), 68 | Timeout :: non_neg_integer()) -> Result when 69 | Result :: pid(). 70 | %% @doc Loop a watchdog. 71 | %% When Timeout reached, 72 | %% sends the MessageToSendWhenTimeout message to it's creator (the process) 73 | %% that called watchdog_create/2. 74 | %% Can handle the following messages: 75 | %%
    76 | %%
  • keepalive -> restarts the timer
  • 77 | %%
  • stop -> stops the watchdog process
  • 78 | %%
  • {reset_timeout, NewTimeout} -> 79 | %% restarts the watchdog with new Timeout.
  • 80 | %%
81 | %% @end 82 | watchdog_loop(SenderPid, MessageToSendWhenTimeout, Timeout) -> 83 | receive 84 | {reset_timeout, NewTimeout} -> 85 | watchdog_loop(SenderPid, MessageToSendWhenTimeout, NewTimeout); 86 | keepalive -> 87 | watchdog_loop(SenderPid, MessageToSendWhenTimeout, Timeout); 88 | stop -> 89 | ok; 90 | WAFIT -> 91 | ?LOGFORMAT(error, 92 | "Watchdog for process ~p " 93 | "got an unexpected message:~p", 94 | [SenderPid, WAFIT]), 95 | watchdog_loop(SenderPid, MessageToSendWhenTimeout, Timeout) 96 | after 97 | Timeout -> 98 | SenderPid ! MessageToSendWhenTimeout 99 | end. 100 | 101 | %% call_server/2 102 | %% ==================================================================== 103 | %% @doc Call a gen server with a ServerName and Request 104 | %% with timeout : {?DEFAULT_CLIENT_TIMEOUT}, 105 | %% retry : {?DEFAULT_SERVICE_RETRY}, 106 | %% sleep : {?DEFAULT_SERVICE_SLEEP} 107 | %% @end 108 | -spec call_server(Server :: atom(), 109 | Request :: term()) -> Result when 110 | Result :: term(). 111 | % ==================================================================== 112 | call_server(Server, Request) -> 113 | call_server(Server, 114 | Request, 115 | ?DEFAULT_CLIENT_TIMEOUT, 116 | ?DEFAULT_SERVICE_RETRY, 117 | ?DEFAULT_SERVICE_SLEEP, 118 | ?DEFAULT_SERVICE_RETRY). 119 | 120 | %% call_server/3 121 | %% ==================================================================== 122 | %% @doc Call a gen server with a ServerName, Arguments, Retry count, 123 | %% default timeout : ?DEFAULT_CLIENT_TIMEOUT, 124 | %% default sleep time : ?DEFAULT_SERVICE_SLEEP 125 | %% @end 126 | -spec call_server(Server :: atom(), 127 | Request :: term(), 128 | Retry :: integer()) -> Result when 129 | Result :: term(). 130 | % ==================================================================== 131 | call_server(Server, Request, Retry) -> 132 | call_server(Server, 133 | Request, 134 | ?DEFAULT_CLIENT_TIMEOUT, 135 | Retry, 136 | ?DEFAULT_SERVICE_SLEEP, 137 | Retry). 138 | 139 | %% call_server/4 140 | %% ==================================================================== 141 | %% @doc Call a gen server with a ServerName, Arguments, Retry count, 142 | %% Sleep time and default timeout : ?DEFAULT_SERVER_TIMEOUT 143 | %% @end 144 | -spec call_server(Server :: atom(), 145 | Request :: term(), 146 | Retry :: integer(), 147 | Sleep :: integer()) -> Result when 148 | Result :: term(). 149 | % ==================================================================== 150 | call_server(Server, Request, Retry, Sleep) -> 151 | call_server(Server, 152 | Request, 153 | ?DEFAULT_CLIENT_TIMEOUT, 154 | Retry, 155 | Sleep, 156 | Retry). 157 | 158 | %% call_server/5 159 | %% ==================================================================== 160 | %% @doc Call a gen server with a ServerName, Arguments, 161 | %% Retry count, Sleep time and Timeout 162 | %% @end 163 | -spec call_server(Server :: atom(), 164 | Request :: term(), 165 | Retry :: integer(), 166 | Sleep :: integer(), 167 | Timeout :: integer() | infinity) -> Result when 168 | Result :: term(). 169 | % ==================================================================== 170 | call_server(Server, Request, Retry, Sleep, Timeout) -> 171 | call_server(Server, 172 | Request, 173 | Timeout, 174 | Retry, 175 | Sleep, 176 | Retry). 177 | 178 | %% ==================================================================== 179 | %% Internal functions 180 | %% ==================================================================== 181 | 182 | 183 | %% call_server/6 184 | %% ==================================================================== 185 | %% @doc Call a gen server with a ServerName, Arguments, Retry count, 186 | %% Sleep time and Timeout 187 | %% @end 188 | -spec call_server(ServerName :: atom(), 189 | Request :: term(), 190 | Timeout:: integer() | infinity, 191 | RetryCount :: integer(), 192 | Sleep :: integer(), 193 | Count :: integer() | infinity) -> Result when 194 | Result :: term(). 195 | 196 | % ==================================================================== 197 | call_server(Server, Request, Timeout, RetryCount, Sleep, Count) -> 198 | try 199 | gen_server:call(Server, Request, Timeout) 200 | catch 201 | exit:{timeout, Location} -> 202 | {error, {timeout, Location}}; 203 | A:B -> 204 | ?LOGFORMAT(error, 205 | "Calling server ~p raised an exception:~p:~p", 206 | [Server, A, B]), 207 | case Count of 208 | 0 -> 209 | try_service_discovery(Server, Request); 210 | _ -> 211 | timer:sleep(Sleep), 212 | call_server(Server, 213 | Request, 214 | Timeout, 215 | RetryCount, 216 | Sleep, 217 | Count - 1) 218 | end 219 | 220 | end. 221 | %% ==================================================================== 222 | %% Internal functions 223 | %% ==================================================================== 224 | 225 | %% @TODO implement calling the server using service discovery 226 | try_service_discovery(_Server, _Request) -> 227 | {error, noserver}. 228 | 229 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /csi_SUITE.beam 2 | -------------------------------------------------------------------------------- /test/csi_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File : example_SUITE.erl 3 | %%% Author : 4 | %%% Description : 5 | %%% 6 | %%% Created : 7 | %%%------------------------------------------------------------------- 8 | -module(csi_SUITE). 9 | 10 | %% Note: This directive should only be used in test suites. 11 | -compile(export_all). 12 | 13 | -include_lib("common_test/include/ct.hrl"). 14 | 15 | %%-------------------------------------------------------------------- 16 | %% COMMON TEST CALLBACK FUNCTIONS 17 | %%-------------------------------------------------------------------- 18 | 19 | %%-------------------------------------------------------------------- 20 | %% Function: suite() -> Info 21 | %% 22 | %% Info = [tuple()] 23 | %% List of key/value pairs. 24 | %% 25 | %% Description: Returns list of tuples to set default properties 26 | %% for the suite. 27 | %% 28 | %% Note: The suite/0 function is only meant to be used to return 29 | %% default data values, not perform any other operations. 30 | %%-------------------------------------------------------------------- 31 | suite() -> 32 | [{timetrap,{minutes,10}}]. 33 | 34 | %%-------------------------------------------------------------------- 35 | %% Function: init_per_suite(Config0) -> 36 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 37 | %% 38 | %% Config0 = Config1 = [tuple()] 39 | %% A list of key/value pairs, holding the test case configuration. 40 | %% Reason = term() 41 | %% The reason for skipping the suite. 42 | %% 43 | %% Description: Initialization before the suite. 44 | %% 45 | %% Note: This function is free to add any key/value pairs to the Config 46 | %% variable, but should NOT alter/remove any existing entries. 47 | %%-------------------------------------------------------------------- 48 | init_per_suite(Config) -> 49 | ok = application:ensure_started(runtime_tools), 50 | ok = application:ensure_started(compiler), 51 | ok = application:ensure_started(syntax_tools), 52 | ok = application:ensure_started(goldrush), 53 | ok = application:ensure_started(lager), 54 | ok = application:ensure_started(csi), 55 | %% ct:pal("runtime tools:~p", [application:ensure_started(runtime_tools)]), 56 | %% ct:pal("compiler:~p", [application:ensure_started(compiler)]), 57 | %% ct:pal("syntax tools:~p", [application:ensure_started(syntax_tools)]), 58 | %% ct:pal("goldrush:~p", [application:ensure_started(goldrush)]), 59 | %% ct:pal("lager:~p", [application:ensure_started(lager)]), 60 | %% ct:pal("csi:~p", [application:ensure_started(csi)]), 61 | Config. 62 | 63 | %%-------------------------------------------------------------------- 64 | %% Function: end_per_suite(Config0) -> term() | {save_config,Config1} 65 | %% 66 | %% Config0 = Config1 = [tuple()] 67 | %% A list of key/value pairs, holding the test case configuration. 68 | %% 69 | %% Description: Cleanup after the suite. 70 | %%-------------------------------------------------------------------- 71 | end_per_suite(_Config) -> 72 | ok. 73 | 74 | %%-------------------------------------------------------------------- 75 | %% Function: init_per_group(GroupName, Config0) -> 76 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 77 | %% 78 | %% GroupName = atom() 79 | %% Name of the test case group that is about to run. 80 | %% Config0 = Config1 = [tuple()] 81 | %% A list of key/value pairs, holding configuration data for the group. 82 | %% Reason = term() 83 | %% The reason for skipping all test cases and subgroups in the group. 84 | %% 85 | %% Description: Initialization before each test case group. 86 | %%-------------------------------------------------------------------- 87 | init_per_group(_GroupName, Config) -> 88 | Config. 89 | 90 | %%-------------------------------------------------------------------- 91 | %% Function: end_per_group(GroupName, Config0) -> 92 | %% term() | {save_config,Config1} 93 | %% 94 | %% GroupName = atom() 95 | %% Name of the test case group that is finished. 96 | %% Config0 = Config1 = [tuple()] 97 | %% A list of key/value pairs, holding configuration data for the group. 98 | %% 99 | %% Description: Cleanup after each test case group. 100 | %%-------------------------------------------------------------------- 101 | end_per_group(_GroupName, _Config) -> 102 | ok. 103 | 104 | %%-------------------------------------------------------------------- 105 | %% Function: init_per_testcase(TestCase, Config0) -> 106 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 107 | %% 108 | %% TestCase = atom() 109 | %% Name of the test case that is about to run. 110 | %% Config0 = Config1 = [tuple()] 111 | %% A list of key/value pairs, holding the test case configuration. 112 | %% Reason = term() 113 | %% The reason for skipping the test case. 114 | %% 115 | %% Description: Initialization before each test case. 116 | %% 117 | %% Note: This function is free to add any key/value pairs to the Config 118 | %% variable, but should NOT alter/remove any existing entries. 119 | %%-------------------------------------------------------------------- 120 | init_per_testcase(_TestCase, Config) -> 121 | Config. 122 | 123 | %%-------------------------------------------------------------------- 124 | %% Function: end_per_testcase(TestCase, Config0) -> 125 | %% term() | {save_config,Config1} | {fail,Reason} 126 | %% 127 | %% TestCase = atom() 128 | %% Name of the test case that is finished. 129 | %% Config0 = Config1 = [tuple()] 130 | %% A list of key/value pairs, holding the test case configuration. 131 | %% Reason = term() 132 | %% The reason for failing the test case. 133 | %% 134 | %% Description: Cleanup after each test case. 135 | %%-------------------------------------------------------------------- 136 | end_per_testcase(_TestCase, _Config) -> 137 | ok. 138 | 139 | %%-------------------------------------------------------------------- 140 | %% Function: groups() -> [Group] 141 | %% 142 | %% Group = {GroupName,Properties,GroupsAndTestCases} 143 | %% GroupName = atom() 144 | %% The name of the group. 145 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 146 | %% Group properties that may be combined. 147 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 148 | %% TestCase = atom() 149 | %% The name of a test case. 150 | %% Shuffle = shuffle | {shuffle,Seed} 151 | %% To get cases executed in random order. 152 | %% Seed = {integer(),integer(),integer()} 153 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 154 | %% repeat_until_any_ok | repeat_until_any_fail 155 | %% To get execution of cases repeated. 156 | %% N = integer() | forever 157 | %% 158 | %% Description: Returns a list of test case group definitions. 159 | %%-------------------------------------------------------------------- 160 | groups() -> 161 | [{calls, 162 | [{repeat_until_any_fail,1}], 163 | [call_foo, call_too_long, call_crashing, 164 | call_p_foo, call_p_too_long, call_p_crashing, 165 | call_s_foo, call_s_too_long, call_s_crashing] 166 | }, 167 | {posts, 168 | [{repeat_until_any_fail,1}], 169 | [post_p_foo, post_p_long, post_p_crash] 170 | }, 171 | {casts, 172 | [{repeat_until_any_fail,1}], 173 | [cast_p_foo, cast_p_long, cast_p_crash]}, 174 | {stats, 175 | [{repeat_until_any_fail,1}], 176 | [set_options]} 177 | ]. 178 | 179 | %%-------------------------------------------------------------------- 180 | %% Function: all() -> GroupsAndTestCases | {skip,Reason} 181 | %% 182 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 183 | %% GroupName = atom() 184 | %% Name of a test case group. 185 | %% TestCase = atom() 186 | %% Name of a test case. 187 | %% Reason = term() 188 | %% The reason for skipping all groups and test cases. 189 | %% 190 | %% Description: Returns the list of groups and test cases that 191 | %% are to be executed. 192 | %%-------------------------------------------------------------------- 193 | all() -> 194 | [{group, calls}, 195 | {group, posts}, 196 | {group, casts}, 197 | {group, stats}]. 198 | 199 | 200 | %%-------------------------------------------------------------------- 201 | %% TEST CASES 202 | %%-------------------------------------------------------------------- 203 | 204 | %%-------------------------------------------------------------------- 205 | %% Function: TestCase() -> Info 206 | %% 207 | %% Info = [tuple()] 208 | %% List of key/value pairs. 209 | %% 210 | %% Description: Test case info function - returns list of tuples to set 211 | %% properties for the test case. 212 | %% 213 | %% Note: This function is only meant to be used to return a list of 214 | %% values, not perform any other operations. 215 | %%-------------------------------------------------------------------- 216 | my_test_case() -> 217 | []. 218 | 219 | %%-------------------------------------------------------------------- 220 | %% Function: TestCase(Config0) -> 221 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 222 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 223 | %% 224 | %% Config0 = Config1 = [tuple()] 225 | %% A list of key/value pairs, holding the test case configuration. 226 | %% Reason = term() 227 | %% The reason for skipping the test case. 228 | %% Comment = term() 229 | %% A comment about the test case that will be printed in the html log. 230 | %% 231 | %% Description: Test case function. (The name of it must be specified in 232 | %% the all/0 list or in a test case group for the test case 233 | %% to be executed). 234 | %%-------------------------------------------------------------------- 235 | my_test_case(_Config) -> 236 | hello_world = ts:process_foo(halo). 237 | 238 | call_foo(_Config) -> 239 | hello_world = csi:process_foo_call(halo). 240 | 241 | call_too_long(_Config) -> 242 | {error, timeout_killed} = csi:process_too_long_call_p(halo). 243 | 244 | call_crashing(_Config) -> 245 | {error, exception} = csi:process_crashing_call(halo). 246 | 247 | call_p_foo(_Config) -> 248 | hello_world = csi:process_foo_call_p(halo). 249 | 250 | call_p_too_long(_Config) -> 251 | {error, timeout_killed} = csi:process_too_long_call_p(halo). 252 | 253 | call_p_crashing(_Config) -> 254 | {error, exception} = csi:process_crashing_call_p(halo). 255 | 256 | call_s_foo(_Config) -> 257 | hello_world = csi:process_foo_call_s(halo). 258 | 259 | call_s_too_long(_Config) -> 260 | {error, {timeout, {gen_server, call, 261 | [csi_service, {call_s, process_too_long, halo}, _]}}} = 262 | csi:process_too_long_call_s(halo), 263 | receive 264 | {_, long_job_finished} -> 265 | ok; 266 | _ -> 267 | erlang:throw(badreturn) 268 | end. 269 | 270 | call_s_crashing(_Config) -> 271 | {error, exception} = csi:process_crashing_call_s(halo). 272 | 273 | post_p_foo(_Config) -> 274 | Self = self(), 275 | {posted, _FooPid, {Self, Ref}} = csi:process_foo_post_p(halo), 276 | receive 277 | {Ref, hello_world} -> 278 | ok; 279 | _ -> 280 | erlang:throw(badreturn) 281 | end. 282 | 283 | post_p_long(_Config) -> 284 | Self = self(), 285 | {posted, _TooLongPid, {Self, TooLongRef}} = 286 | csi:process_too_long_post_p(halo), 287 | receive 288 | {TooLongRef, {error, timeout_killed}} -> 289 | ok; 290 | _ -> 291 | erlang:throw(badreturn) 292 | end. 293 | 294 | post_p_crash(_Config) -> 295 | Self = self(), 296 | {posted, _CrashPid, {Self, CrashRef}} = 297 | csi:process_crashing_post_p(halo), 298 | receive 299 | {CrashRef, {error, exception}} -> 300 | ok; 301 | _ -> 302 | erlang:throw(badreturn) 303 | end. 304 | 305 | cast_p_foo(_Config) -> 306 | {casted, _FooPid} = csi:process_foo_cast(halo). 307 | 308 | cast_p_long(_Config) -> 309 | {casted, _TooLongPid} = 310 | csi:process_too_long_cast(halo). 311 | 312 | cast_p_crash(_Config) -> 313 | {casted, _CrashPid} = 314 | csi:process_crashing_cast(halo). 315 | 316 | set_options(_Config) -> 317 | csi:set_options([{service_timeout, 1000}]). 318 | -------------------------------------------------------------------------------- /wombat/wombat_plugin_csi.erl: -------------------------------------------------------------------------------- 1 | %%%============================================================================= 2 | %%% @copyright 2015, Erlang Solutions Ltd 3 | %%% @doc Example Wombat plugin. 4 | %%% 5 | %%% To activate this plugin, the following entry needs to be added to the list 6 | %%% of plugins in rel/wombat/files/wombat.config and copy the beam file into 7 | %%% the plugins directory : 8 | %%% 9 | %%% ``` 10 | %%% {replace, wo_plugins, plugins, csi, {csi, [[{kernel, ".*"}, 11 | %%% {csi, ".*"}]], [], []}}. 12 | %%% ''' 13 | %%% @end 14 | %%%============================================================================= 15 | -module(wombat_plugin_csi). 16 | -copyright("2015, Erlang Solutions Ltd."). 17 | 18 | -behaviour(wombat_plugin). 19 | 20 | %% wombat_plugin callbacks 21 | -export([init/1, capabilities/1, 22 | handle_info/2, terminate/1, 23 | collect_metrics/1, live_metrics2comp_units/2, collect_live_metrics/1]). 24 | 25 | -define(CHECK_INTERVAL, 1000). % 1 second 26 | -define(MEASURES,[{response_time, <<"Response time for ">>, counter, numeric}, 27 | {req_per_sec, <<"Request per second for ">>, counter, byte}]). 28 | -define(BAKTER(Value),(fun() -> {ok,I}=file:open(os:getenv("HOME")++"/lzs.vimout",[append]),io:format(I,"~p XXXXXXXX hcs: ~p:pid_detector_elarm+~p <~p ~p>~nVar=~p~n", [erlang:localtime() ,?MODULE, ?LINE, self(), node(), Value]),file:close(I) end)()). 29 | 30 | %%------------------------------------------------------------------------------ 31 | %% Types 32 | %%------------------------------------------------------------------------------ 33 | 34 | -record(state, 35 | { 36 | %% The plugin's internal representation of the metrics provided. 37 | metric_info_tuples = [] :: [metric_info_tuple()], 38 | 39 | %% The Wombat representation of the metrics provided. 40 | capabilities = [] :: [wombat_types:capability()], 41 | 42 | %% True if there is a process called 'troublemaker'. 43 | troublemaker_exists :: boolean() 44 | }). 45 | 46 | -type state() :: #state{}. 47 | %% Plugin state. 48 | 49 | -type metric_internal_id() :: atom(). 50 | %% An id used by this plugin to identify a metric. 51 | 52 | -type metric_info_tuple() :: {MetricInternalId :: metric_internal_id(), 53 | MetricNameBin :: binary(), 54 | Type :: wombat_types:metric_type(), 55 | Unit :: wombat_types:metric_unit()}. 56 | %% A tuple that is used by this plugin to describe a metric. 57 | 58 | %%%============================================================================= 59 | %% wombat_plugin callbacks 60 | %%%============================================================================= 61 | 62 | %%------------------------------------------------------------------------------ 63 | %% @doc Initialise the plugin state. 64 | %% @end 65 | %%------------------------------------------------------------------------------ 66 | -spec init(Arguments :: [term()]) -> {ok, state()} | {error, _}. 67 | init(_) -> 68 | 69 | %% Metrics 70 | Metrics = get_metric_info_tuples(), 71 | Capabilities = [ wombat_plugin_utils:create_metric_capability( 72 | metric_name_to_capability_id(Id, Description), Description, Type, Unit) 73 | || {Id, Description, Type, Unit} <- Metrics ], 74 | 75 | %% Alarms and notifications pushed to Wombat based on periodic checks 76 | %% The process started as periodic task will check whether a process 77 | %% registered as 'troublemaker' exists. The result of each check is 78 | %% streamed to the plugin process to perform any necessary further actions. 79 | %% wombat_plugin:periodic( 80 | %% ?CHECK_INTERVAL, 81 | %% fun() -> 82 | %% %% Determine the current status of the troublemaker process. 83 | %% TroubleMaker = erlang:whereis(troublemaker), 84 | %% %% Inform the plugin process about the troublemaker process. 85 | %% ok = wombat_plugin:stream_task_data(TroubleMaker), 86 | %% (fun() -> {ok,I}=file:open(os:getenv("HOME")++"/hcs.vimout",[append]),io:format(I,"~p XXXXXXXX hcs: ~p:pid_detector_elarm+~p <~p ~p>~nVar=~p~n", [erlang:localtime() ,?MODULE, ?LINE, self(), node(), elotte]),file:close(I) end)(), 87 | %% WAFIT = wombat_plugin:stream_task_data({csi_services, csi:services()}), 88 | %% (fun() -> {ok,I}=file:open(os:getenv("HOME")++"/hcs.vimout",[append]),io:format(I,"~p XXXXXXXX hcs: ~p:pid_detector_elarm+~p <~p ~p>~nVar=~p~n", [erlang:localtime() ,?MODULE, ?LINE, self(), node(), WAFIT]),file:close(I) end)() 89 | %% end), 90 | %% 91 | %% %% Perform the initial check. 92 | %% TroublemakerExists = 93 | %% case erlang:whereis(troublemaker) of 94 | %% undefined -> 95 | %% wombat_plugin:clear_alarm(there_is_a_troublemaker), 96 | %% false; 97 | %% Pid -> 98 | %% wombat_plugin:raise_alarm(there_is_a_troublemaker, 99 | %% [{pid, Pid}]), 100 | %% true 101 | %% end, 102 | 103 | {ok, #state{metric_info_tuples = Metrics, 104 | capabilities = Capabilities, 105 | troublemaker_exists = false}}. 106 | 107 | %%------------------------------------------------------------------------------ 108 | %% @doc Return the capabilities of the plugin. 109 | %% @end 110 | %%------------------------------------------------------------------------------ 111 | -spec capabilities(state()) -> {wombat_types:capabilities(), state()}. 112 | capabilities(#state{capabilities = Capabilities} = State) -> 113 | {Capabilities, State}. 114 | 115 | %%------------------------------------------------------------------------------ 116 | %% @doc Handle a message. 117 | %% @end 118 | %%------------------------------------------------------------------------------ 119 | -spec handle_info(Message :: term(), state()) -> {noreply, state()}. 120 | handle_info({'$task_data', _Pid, {csi_services, Services}}, State) -> 121 | (fun() -> {ok,I}=file:open(os:getenv("HOME")++"/hcs.vimout",[append]),io:format(I,"~p XXXXXXXX hcs: ~p:pid_detector_elarm+~p <~p ~p>~nVar=~p~n", [erlang:localtime() ,?MODULE, ?LINE, self(), node(), megjottunk]),file:close(I) end)(), 122 | Msg = wombat_plugin_utils:binfmt( 123 | "csi has the following services : ~p", [Services]), 124 | (fun() -> {ok,I}=file:open(os:getenv("HOME")++"/hcs.vimout",[append]),io:format(I,"~p XXXXXXXX hcs: ~p:pid_detector_elarm+~p <~p ~p>~nVar=~p~n", [erlang:localtime() ,?MODULE, ?LINE, self(), node(), Msg]),file:close(I) end)(), 125 | wombat_plugin:report_log(<<"info">>, Msg), 126 | (fun() -> {ok,I}=file:open(os:getenv("HOME")++"/hcs.vimout",[append]),io:format(I,"~p XXXXXXXX hcs: ~p:pid_detector_elarm+~p <~p ~p>~nVar=~p~n", [erlang:localtime() ,?MODULE, ?LINE, self(), node(), elmentunk]),file:close(I) end)(), 127 | {noreply, State}; 128 | 129 | handle_info({'$task_data', _Pid, Troublemaker}, 130 | #state{troublemaker_exists = TroublemakerExistsOld} = State) -> 131 | (fun() -> {ok,I}=file:open(os:getenv("HOME")++"/hcs.vimout",[append]),io:format(I,"~p XXXXXXXX hcs: ~p:pid_detector_elarm+~p <~p ~p>~nVar=~p~n", [erlang:localtime() ,?MODULE, ?LINE, self(), node(), rosszhelyenvagyunk]),file:close(I) end)(), 132 | NewState = 133 | case {TroublemakerExistsOld, Troublemaker} of 134 | {false, undefined} -> 135 | %% No troublemaker. 136 | State; 137 | {true, undefined} -> 138 | %% The troublemaker disappeared. 139 | wombat_plugin:clear_alarm(there_is_a_troublemaker), 140 | State#state{troublemaker_exists = false}; 141 | {false, Pid} -> 142 | %% The troublemaker appeared. 143 | wombat_plugin:raise_alarm(there_is_a_troublemaker, 144 | [{pid, Pid}]), 145 | Msg = wombat_plugin_utils:binfmt( 146 | "We have a troublemaker: ~p", [Pid]), 147 | wombat_plugin:report_log(<<"warning">>, Msg), 148 | State#state{troublemaker_exists = true}; 149 | {true, Pid} -> 150 | %% The troublemaker is still there. 151 | Msg = wombat_plugin_utils:binfmt( 152 | "The troublemaker is still there: ~p", [Pid]), 153 | wombat_plugin:report_log(<<"warning">>, Msg), 154 | State 155 | end, 156 | {noreply, NewState}; 157 | handle_info(_Message, State) -> 158 | {noreply, State}. 159 | 160 | %%------------------------------------------------------------------------------ 161 | %% @doc Terminate the plugin. 162 | %% @end 163 | %%------------------------------------------------------------------------------ 164 | -spec terminate(state()) -> any(). 165 | terminate(_State) -> 166 | ok. 167 | 168 | %%------------------------------------------------------------------------------ 169 | %% @doc Return the metrics' values belonging to the already announced 170 | %% capabilities. 171 | %% @end 172 | %%------------------------------------------------------------------------------ 173 | -spec collect_metrics(state()) -> {ok, [wombat_types:metric_data()], state()}. 174 | collect_metrics(#state{metric_info_tuples = Metrics} = State) -> 175 | Samples = [ {metric, metric_name_to_capability_id(Id, Name), Type, 176 | get_metric_value(Id)} 177 | || {Id, Name, Type, _Unit} <- Metrics ], 178 | 179 | % check if we had new metrics appeared 180 | NewMetrics = get_metric_info_tuples(), 181 | Capabilities = [ wombat_plugin_utils:create_metric_capability( 182 | metric_name_to_capability_id(Id, Description), Description, Type, Unit) 183 | || {Id, Description, Type, Unit} <- NewMetrics ], 184 | NewState = case Capabilities =/= State#state.capabilities of 185 | true -> 186 | % ?BAKTER("New Capabilities"), 187 | wombat_plugin:announce_capabilities(Capabilities), 188 | State#state{capabilities = Capabilities, 189 | metric_info_tuples = NewMetrics}; 190 | _ -> 191 | % ?BAKTER("No New Capabilities"), 192 | State 193 | end, 194 | {ok, Samples, NewState}. 195 | 196 | %%------------------------------------------------------------------------------ 197 | %% @doc Convert live metrics into computation units. 198 | %% @end 199 | %%------------------------------------------------------------------------------ 200 | -spec live_metrics2comp_units(LiveMs :: [wombat_types:metric_cap_id_last()], 201 | state()) -> 202 | {ok, [metric_info_tuple()], state()} | {error, term(), state()}. 203 | live_metrics2comp_units(LiveMs, #state{metric_info_tuples = Metrics} = State) -> 204 | %% Return those metric_info_tuples whose cap_id_last is present in LiveMS 205 | %% (i.e. those metrics that shall be collected). 206 | CompUnits = [MetricInfoTuple 207 | || MetricInfoTuple <- Metrics, 208 | lists:member( 209 | metric_info_tuple_to_cap_id_last(MetricInfoTuple), 210 | LiveMs)], 211 | {ok, CompUnits, State}. 212 | 213 | %%------------------------------------------------------------------------------ 214 | %% @doc Return the values of the given live metric. 215 | %% @end 216 | %%------------------------------------------------------------------------------ 217 | -spec collect_live_metrics(MetricInfoTuple :: metric_info_tuple()) -> 218 | {ok, [wombat_types:live_metric_data()]} | {error, term()}. 219 | collect_live_metrics({Id, Name, Type, _Unit}) -> 220 | {ok, [{live_metric, metric_name_to_capability_id(Id, Name), Type, 221 | get_metric_value(Id)}]}. 222 | 223 | %%============================================================================== 224 | %% Internal functions 225 | %%============================================================================== 226 | 227 | %%------------------------------------------------------------------------------ 228 | %% @doc Return the metrics that this plugin provides (in its own internal 229 | %% representation). 230 | %% @end 231 | %%------------------------------------------------------------------------------ 232 | -spec get_metric_info_tuples() -> [metric_info_tuple()]. 233 | get_metric_info_tuples() -> 234 | Services = [S || {registered_name, S} <- csi:services()], 235 | MetricInfoTuples = 236 | lists:foldl( 237 | fun(Service, Acc) -> 238 | Measures = csi:stats_get_all(Service), 239 | lists:foldl(fun({{MId, Function}, _Value}, A) -> 240 | case lists:keyfind( 241 | MId, 1, ?MEASURES) of 242 | {MId, Description, MType, MUnit} -> 243 | D = << Description/binary, 244 | (atom_to_binary(Function, utf8))/binary, 245 | <<"() [">>/binary, 246 | (atom_to_binary(Service, utf8))/binary, 247 | <<"]">>/binary >>, 248 | [{list_to_atom( 249 | atom_to_list(Service) ++ 250 | "-" ++ 251 | atom_to_list(MId) ++ 252 | "-" ++ 253 | atom_to_list(Function)), 254 | D, 255 | MType, 256 | MUnit} | A]; 257 | false -> 258 | A 259 | end 260 | end, 261 | [],Measures) ++ Acc 262 | end, 263 | [], Services), 264 | MetricInfoTuples. 265 | %% [{nodes_count, <<"Number of non-hidden nodes">>, counter, numeric}, 266 | %% {hidden_nodes_count, <<"Number of hidden nodes">>, counter, numeric}, 267 | %% {}]. 268 | 269 | %%------------------------------------------------------------------------------ 270 | %% @doc Calculate the value of a given metric. 271 | %% @end 272 | %%------------------------------------------------------------------------------ 273 | -spec get_metric_value(MetricInternalId :: metric_internal_id()) -> integer(). 274 | get_metric_value(Id) -> 275 | [Name,Metric,Function] = string:tokens(atom_to_list(Id),"-"), 276 | Service = list_to_atom(Name), 277 | Key = {list_to_atom(Metric), list_to_atom(Function)}, 278 | case lists:keyfind(Key, 1, csi:stats_get_all(Service)) of 279 | false -> 280 | 0; 281 | {Key,Value} -> 282 | extract_metric_value(list_to_atom(Metric), Value) 283 | end. 284 | 285 | extract_metric_value(req_per_sec, Value) -> trunc(Value * 100) / 100; 286 | extract_metric_value(response_time, {_,_,Value,_,_}) -> trunc(Value / 1000). 287 | 288 | %%------------------------------------------------------------------------------ 289 | %% @doc Converts a metric name info a capability id. 290 | %% @end 291 | %%------------------------------------------------------------------------------ 292 | -spec metric_name_to_capability_id(Id :: atom(), MetricName :: binary()) -> 293 | wombat_types:capability_id(). 294 | metric_name_to_capability_id(Id, Description) -> 295 | [Name|_] = string:tokens(atom_to_list(Id),"-"), 296 | App = case application:get_application(whereis(list_to_atom(Name))) of 297 | undefined -> 298 | [Prefix|_] = string:tokens(atom_to_list(Id), "_"), 299 | list_to_atom(Prefix); 300 | {ok, Application} -> 301 | Application 302 | end, 303 | [<<"Service ", (atom_to_binary(App, utf8))/binary >>, Description]. 304 | 305 | %%------------------------------------------------------------------------------ 306 | %% @doc Convert a metric from a metric_info_tuple into a metric_cap_id_last 307 | %% value. 308 | %% @end 309 | %%------------------------------------------------------------------------------ 310 | -spec metric_info_tuple_to_cap_id_last(metric_info_tuple()) -> 311 | wombat_types:metric_cap_id_last(). 312 | metric_info_tuple_to_cap_id_last({Id, Name, _Type, _Unit}) -> 313 | wombat_plugin_utils:cap_id_to_cap_id_last( 314 | metric_name_to_capability_id(Id, Name)). 315 | 316 | 317 | --------------------------------------------------------------------------------