├── .eslintrc.js ├── .gitignore ├── README.md ├── README.zh.md ├── bin └── node-agent ├── doc └── architecture.png ├── lib ├── CLI.js ├── God.js ├── ProcessContainer.js ├── constants.js ├── log.js ├── tars │ ├── HttpStats │ │ └── index.js │ ├── Message │ │ ├── AdminF.js │ │ ├── AdminF.tars │ │ ├── AdminFImp.js │ │ └── index.js │ ├── Notify │ │ └── index.js │ ├── Report │ │ ├── NodeF.tars │ │ ├── NodeFProxy.js │ │ └── index.js │ └── UsageStats │ │ └── index.js └── util │ ├── convert.js │ ├── cpu │ ├── index.js │ ├── posix.js │ ├── system.js │ └── win.js │ ├── lsdeps.js │ └── treekill.js └── package.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "rules": { 7 | "no-console" : "off", 8 | "no-empty" : ["error", { "allowEmptyCatch": true }] 9 | } 10 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @ tars / node-agent 2 | 3 | In order for Node.js applications to run in the TARS framework, `node-agent` will be used as a launcher to launch the application and provide the service features required by the production environment. 4 | 5 | It mainly provides the following functions: 6 | 7 | * __Built-in load balancing (implemented through the Cluster module)__ 8 | * __Monitoring and Pulling of Abnormal Exit__ 9 | * __Log collection and processing__ 10 | * __Supports management commands for the TARS platform__ 11 | * __Support HTTP (s) service monitoring and reporting (running on TARS platform)__ 12 | * __Support service usage report (run on TARS platform__ 13 | 14 | ## Installation 15 | 16 | `npm install @ tars/node-agent -g` 17 | 18 | > Since `node-agent` is a CLI program, it usually needs to be installed with the __- g__ parameter 19 | 20 | ## Usage 21 | 22 | `node-agent app.js [options]` 23 | 24 | * app.js is the entry script for the program, see [entry point](#entrypoint) for details 25 | * [options] Optional configuration, see [Options](#options) section 26 | 27 | ## Examples 28 | 29 | Execute the app.js file: 30 | > $ node-agent app.js 31 | 32 | Start with the configuration file for the `TARS` service: 33 | > $ node-agent app.js --config MTT.Test.conf 34 | 35 | Launch and name the app MTT.Test: 36 | > $ node-agent app.js --name MTT.Test 37 | 38 | Define log output path 39 | > $ node-agent app.js --log ./logs/ 40 | 41 | Pass the startup parameters of the child node `node`: 42 | > $ node-agent app.js --node-args = "-debug = 7001" 43 | 44 | Define the number of child processes: 45 | > $ node-agent app.js -i 4 46 | 47 | ## Entry point 48 | 49 | The second parameter passed when `node-agent` starts is used to specify the entry point file for service script execution, where: 50 | 51 |  * Can be directly passed into the script file for execution, such as `./App.js` 52 | 53 |  * You can also pass in the directory where the script file is located, such as `./` 54 | 55 | When a directory is passed in, the entry point is confirmed in the following order: 56 | 57 | 1. The `package.json` file exists in the directory, then: 58 | 1. Find `nodeAgent.main` 59 | 2. Find `script.start` (this configuration section needs to start with` node` to recognize) 60 | 3. Find `main` 61 | 2. Find if it exists in the directory: `server.js`,` app.js`, `start.js`,` index.js` 62 | 63 | As long as one of these matches is executed as an entry point file, no further matching is performed. 64 | 65 | ## options 66 | 67 | 68 | > Options: 69 | > -h, --help output usage information 70 | > -V, --version output the version number 71 | > -c, --config specify tars config file. NOTE: independent config will be override this 72 | > -n, --name set a for script - e.g. app.servername 73 | > -l, --log specify log file 74 | > -i, --instances launch [number] instances (for networked app)(load balanced) 75 | > --env specify environment to get specific env variables (for JSON declaration) 76 | > --http-address specify http ip:port address to pass to script - e.g. 127.0.0.1:80 77 | > --script-args space delimited arguments to pass to script - e.g. --use="https" 78 | > --node-args space delimited arguments to pass to node - e.g. --node-args="--debug=7001 --trace-deprecation" 79 | > --run-as-user The user or uid to run a managed process as 80 | > --run-as-group The group or gid to run a managed process as 81 | > --max-memory-restart specify max memory amount used to autorestart (in megaoctets) 82 | > --graceful-shutdown specify graceful shutdown timeout (in millisecond), default is 8000ms 83 | > --exception-max The program will be terminated if an exceeding max exception count, default is 5 84 | > --exception-time The program will be terminated if an exception occurs within a particular period of time, default is 5000ms 85 | > --keepalive-time specify the interval for detecting the worker which could be set to [off] if you want to debug and the default value is 60s 86 | > --applog-max-files specify max number of rolling log, default is 10 87 | > --applog-max-size specify max file size for each rolling log, use human readable unit in [K|G|M], default is 10M 88 | > --applog-level define log level, default is DEBUG 89 | > --tars-node set tars node conncetion string, agent would send notifications to tars node - e.g. tars.tarsnode.ServerObj@tcp -h 127.0.0.1 -p 10000 -t 60000 90 | > --tars-local set local interface setup string, agent would receive the notifications from tars node - e.g. tcp -h 127.0.0.1 -p 10000 -t 3000 91 | > --tars-monitor enable or disable service monitor running in tars platform, and the default value is on 92 | > --tars-monitor-http-threshold if the http(s) status code is large than the preseted threshold then this request will be considered error. default threshold is 400, set it "off" to disabled 93 | > --tars-monitor-http-seppath separate url pathname as interface name, default is on 94 | > --tars-monitor-http-socketerr considered socket error as error, default is on 95 | > --long-stack enable long stack trace to auto append asynchronous stack, default is off 96 | > --long-stack-filter-usercode filter long stack trace keep user module code only, default is off 97 | 98 | ### -c, --config 99 | 100 | If this service is a TARS service, you can specify the service's profile here. 101 | 102 | The configuration file will be automatically read in as the basic configuration. You can override the imported basic configuration by setting other configuration parameters. 103 | 104 | ### -n, --name 105 | 106 | You can specify the service name here. 107 | 108 | * If not configured, use _script filename_ 109 | * For TARS service, the service name must be in the format _app.serverName_ 110 | 111 | ### -l, --log 112 | 113 | Specify the root directory of the output log file 114 | 115 | If not configured, all log output is _stdout / stderr_ output 116 | 117 | ### -i, --instances 118 | 119 | `node-agent` uses Node.js'native [Cluster](http://www.nodejs.org/api/cluster.html "Cluster") module to implement load balancing. 120 | 121 | The number of child processes (business processes) started by `node-agent` can be configured here: 122 | 123 | * Not configured (or configured as `auto`,`0`), the number of child processes started is equal to the `CPU physical core` number. 124 | 125 | * Configured as `max`, the number of child processes started equals the number of CPUs (all cores). 126 | 127 | If `node-agent` is started by`tarsnode`, the `tars.application.client.asyncthread` configuration section in the TARS configuration file is automatically read. 128 | 129 | It can also be adjusted via `TARS Platform-> Edit Services-> Number of Asynchronous Threads`. 130 | 131 | ### --env 132 | 133 | Set the _environment variable_ when the service is started, which needs to be described in `JSON` format 134 | 135 | For example: this configuration can be passed into the current operating environment (development, production) 136 | 137 | ```js 138 | {\ "NODE_ENV \": \ "production \"} 139 | ``` 140 | 141 | __Please note: When passed as a command line parameter, the double quotes (") need to be escaped (\")__ 142 | 143 | If the service is a TARS service, this parameter is read and set in a way that `tarsnode` recognizes. 144 | 145 | ### --http-address 146 | 147 | Set the ip: port required for service script execution 148 | 149 | You can use the environment variables `HTTP_IP` (`IP`), `HTTP_PORT` (`PORT`) in the script to obtain 150 | 151 | ```js 152 | process.env.HTTP_IP 153 | process.env.HTTP_PORT 154 | ``` 155 | 156 | If this service is a TARS service, the value here is the `ip: port` specified in the configuration file by the first non-TARS Servant 157 | 158 | ### --script-args 159 | 160 | Set the parameters required for service script execution 161 | 162 | E.g: 163 | 164 | > $ node-agent app.js --script-args = "-use =" https " 165 | 166 | Equivalent to 167 | 168 | > $ node app.js --use = "https" 169 | 170 | ### --node-args 171 | 172 | Set the startup parameters required by the node cluster child process 173 | 174 | E.g: 175 | 176 | > $ node-agent app.js --node-args = "-debug = 7001 --trace-deprecation" 177 | 178 | Equivalent to 179 | 180 | $ node --debug = 7001 --trace-deprecation app.js 181 | 182 | ### --run-as-user, --run-as-group 183 | 184 | Specify the user (group) for the `node cluster` child process 185 | 186 | This can be used to downgrade the service script. If the permission is not configured, it is equivalent to `node-agent`. 187 | 188 | ### --max-memory-restart 189 | 190 | Specifies the maximum memory that can be used by the service. 191 | 192 | If the child process reaches the maximum memory limit, it will throw an exception and exit. This _ (resource-shaped) _ exception is also handled as an overall exception. 193 | 194 | ### --graceful-shutdown 195 | 196 | Normally, `node-agent` will notify the service via` worker.disconnect() `when stopping the service (process), and let the service release resources and exit. 197 | 198 | You can set the timeout here. If the service (process) does not exit after a given time, `node-agent` will force` kill` to kill the process. 199 | 200 | Timeout is 8 seconds by default 201 | 202 | If `node-agent` is started by` tarsnode`, the `tars.application.server.deactivating-timeout` configuration section in the TARS configuration file is automatically read. 203 | 204 | ### --exception-max, --exception-time 205 | 206 | If the (service) child process exits abnormally, and within a period of time _(-exception-time)_ The number of abnormal exits does not exceed the maximum value _(-exception-max)_. `node-agent` will automatically start a new (service) child process, otherwise` node-agent` and the service will also exit abnormally. 207 | 208 | To facilitate third-party management tools to monitor service status 209 | 210 | --exception-time default is 10s 211 | --exception-max default is 2 212 | 213 | ### --keepalive-time 214 | 215 | If `node-agent` does not receive the heartbeat sent by the (service) child process within a period of time (--keepalive-time), then this (service) child process is determined to be a zombie process and will directly kill `kill` and handle it as an exception. 216 | 217 | _This logic is not triggered when the server `Free Memory` is too small._ 218 | 219 | __If you want to (breakpoint) debug the service script, you need to set this to `--keepalive-time = off`__ 220 | 221 | Its default value is 5m 222 | 223 | ### --applog-max-files, --applog-max-size, --applog-level 224 | 225 | Specify the service's default rolling log size _(-applog-max-size)_, total _(-applog-max-files)_ and log level _(-applog-level)_. 226 | 227 | Two main (rolling) logs are created when the service starts: 228 | 229 | * app.serverName.log: `stdout / stderr / console` of the service started 230 | * app.serverName_agent.log: status information of `node-agent` 231 | 232 | This configuration mainly affects the output parameters of the above two main (rolling) logs 233 | 234 | See [logs](#logs "logs") for details 235 | 236 | ### --tars-node, --tars-local 237 | 238 | If `node-agent` is started by` tarsnode`, you need to specify the RPC connection parameter _(-tars-node)_ of tarsnode and the startup parameter _(-tars-local)_ which is called locally. 239 | 240 | This setting can also be specified via the TARS configuration file _(-tars-config)_. 241 | 242 | `node-agent` will report the service version to`tarsnode` when the service is started, and send heartbeat packets during the service running. 243 | 244 | At the same time, the (started) service started locally by `node-agent` will also receive the issued messages (shutdown / message) from` tarsnode` and respond. 245 | 246 | ### --tars-monitor 247 | 248 | If your service is running on the `TARS` platform,` node-agent` will automatically report service monitoring (usage) information to `tarsstat`. 249 | 250 | The default value is on, set to off to turn off the automatic report function. 251 | 252 | For details, please refer to the “Monitoring and Usage Reporting” section. 253 | 254 | ### --tars-monitor-http-threshold 255 | 256 | If your service's HTTP(s) return code is greater than this threshold, the request will be reported as an abnormal access. 257 | 258 | By default [response.statusCode> = 400](http://www.nodejs.org/api/http.html#http_response_statuscode) is abnormal access. 259 | 260 | Set to off to turn this feature off. 261 | 262 | For details, please refer to the “Monitoring and Usage Reporting” section. 263 | 264 | ### --tars-monitor-http-seppath 265 | 266 | Whether the HTTP (s) service needs to distinguish different paths when reporting. 267 | 268 | The default is to distinguish between paths. The part of url.pathname will be reported as the interface name of the service. 269 | 270 | If your service has very large (large cardinality) pathnames (such as RESTful), you can set it to off. 271 | 272 | For details, please refer to the “Monitoring and Usage Reporting” section. 273 | 274 | ### --tars-monitor-http-socketerr 275 | 276 | By default, the HTTP (s) service accesses [Socket Exception](https://nodejs.org/api/errors.html#errors_common_system_errors) as an exception when reporting. 277 | 278 | If you want to turn this feature off, you can set it to off 279 | 280 | ### --long-stack, --long-stack-filter-usercode 281 | 282 | When this feature is enabled, an asynchronous call stack is automatically attached when an exception occurs, helping to quickly locate asynchronous call problems. 283 | 284 | If you want to filter out the stack generated by user code (module), you can enable `--long-stack-filter-usercode`. 285 | 286 | This feature requires a Node.js version greater than v8.2.x 287 | 288 | __This feature will cause performance loss. Do not enable performance sensitive code.__ 289 | 290 | For details, please see the [LongStack](https://www.npmjs.com/package/longstack) description. 291 | 292 | ## Configuration 293 | 294 | `node-agent` supports startup in multiple configurations: 295 | 296 | * Command line parameters are specified 297 | * Specified in `package.json` of the service script 298 | * Specified in the configuration file of the `TARS` service 299 | 300 | among them: 301 | 302 | * The value specified in the `package.json` or` TARS` configuration file will overwrite the configuration items specified in the command line parameters. 303 | * The configuration parameters can be declared in the configuration section of `nodeAgent` in` package.json` by camel case. 304 | * Declare directly in the configuration file of the `TARS` service as a configuration parameter prototype 305 | 306 | For example (start the child process as nobody): 307 | 308 | Command line parameters: 309 | > node-agent app.js --run-as-user = nobody 310 | 311 | package.json: 312 | >``` js 313 | >{ 314 | > "nodeAgent" : { 315 | > "runAsUser" : "nobody" 316 | > } 317 | >} 318 | >``` 319 | 320 | TARS configuration file: 321 | >```xml 322 | > 323 | > 324 | > 325 | > run-as-user = nobody 326 | > 327 | > 328 | > 329 | > ``` 330 | 331 | ## Messages and events 332 | 333 | In general, user code does not need to process (follow) process messages and events, but if you want to process (response): process exit, TARS management commands, you need to process. 334 | 335 | ### process.on ('disconnect', function) 336 | 337 | For specific description of this event, please refer to [Cluster Event: 'disconnect'](http://www.nodejs.org/api/cluster.html#cluster_event_disconnect) 338 | 339 | By default `node-agent` will process this event, but if user code listens (handles) the event,`node-agent` will no longer process it. 340 | 341 | __Please note: After you process the event, please be sure to call `process.exit()` to ensure that the process can exit normally__ 342 | 343 | ### process.on ('message', object) 344 | 345 | Once `node-agent` receives the management command from` tarsnode`, it will send the process script to the business script. 346 | 347 | The format of the passed message `object` is: 348 | 349 | ```js 350 | { 351 |   cmd: String, 352 |   data: String 353 | } 354 | ``` 355 | 356 | Supported messages `cmd` are: 357 | 358 | * tars.viewstatus: View service status 359 | * tars.setloglevel: Set the log level 360 | * tars.loadconfig: PUSH configuration file 361 | * tars.connection: View the current link situation 362 | * Custom commands 363 | 364 | `Cmd` with` data` exists: 365 | 366 | * tars.setloglevel: `INFO`,` DEBUG`, `WARN`,` ERROR`, `NONE` 367 | * tars.loadconfig: configuration file name 368 | * Custom commands 369 | * process.msg: [all | worker_id]: Custom message object across processes 370 | 371 | \* `node-agent` will split the` custom command`, the characters before the first space in the command are used as `cmd`, and the subsequent parts are used as` data` 372 | 373 | ### process.send (object) 374 | 375 | Send a command to the main process so that the main process performs a specific operation. 376 | 377 | The format of the passed message `object` is the same as the format of the received message. 378 | 379 | #### cmd = process.msg: [all | worker_id] 380 | 381 | With this command, you can send a custom message to the child process specified by the parameter. 382 | 383 | * all: send to all child processes (including themselves) 384 | * worker_id: sent to a specific child process, where worker_id is the `process sequence ID` (process.env.WORKER_ID) 385 | 386 | __ All messages will be relayed through the main process. The main process is likely to become a performance bottleneck under large message volumes. Please use it with caution!__ 387 | 388 | ## Log 389 | 390 | `node-agent` redirects the output of the service (the output of the` stdout | stderr` pipe and the `console` module) to the specified file (when started with the` -l --log` parameter) or pipe. 391 | 392 | The log output is implemented by the [winston-tars](https://github.com/tars-node/winston-tars "winston-tars") module, and the output log format is: `datetime | PID | log level | File name: line number | content` 393 | 394 | The service script can output logs of different levels through the `console` module that comes with` node`. 395 | 396 | console.info = INFO 397 | console.log = DEBUG 398 | console.warn = WARN 399 | console.error = ERROR 400 | 401 | It can also be output through the service's `stdout | stderr` pipe. 402 | 403 | > process.stdout = INFO 404 | > process.stderr = ERROR 405 | 406 | The priority of the log level is: `INFO` <` DEBUG` <`WARN` <` ERROR` <`NONE` 407 | 408 | Among them, the default log level is: `DEBUG` 409 | 410 | ## Environment variables 411 | 412 | `node-agent` provides the required variables to the service script via environment variables: 413 | 414 | * `process.env.IP`: The IP that HTTP (s) can listen on. 415 | * `process.env.PORT`: HTTP (s) listenable port. 416 | * `process.env.WORKER_ID` Process sequence ID (for example, start 8 processes, the first is 0, the second is 1, and so on), the restarted process still uses the previous ID. 417 | 418 | If the service is started by `tarsnode`, the following variables are also supported: 419 | 420 | * `process.env.TARS_CONFIG`: The absolute path where the TARS configuration file used to start the service is located. 421 | * `process.env.TARS_MONITOR`: Whether to enable monitoring (characteristic) reporting (statistics). 422 | 423 | __Please note: all environment variables are of type String__ 424 | 425 | ## Monitoring and reporting 426 | 427 | If your service is running on the `TARS` platform,` node-agent` will automatically report service monitoring (usage) information to `tarsstat`. 428 | 429 | ### Monitoring information 430 | 431 | The reporting of monitoring information is related to the service you started and its caller (can be viewed through `TARS Platform-> Service Monitoring`): 432 | 433 | * HTTP (s) 434 | * Server: [response.statusCode> = 400](http://www.nodejs.org/api/http.html#http_response_statuscode) failed, and the timeout of all requests is 0 435 | * Configurable via [--tars-monitor-http-threshold](# tars-monitor-http-threshold) and [--tars-monitor-http-seppath](# tars-monitor-http-seppath) 436 | 437 | For more details, please visit [@ tars / monitor.stat](https://github.com/tars-node/monitor). 438 | 439 | ### Dosage Information 440 | 441 | No matter what type of service you start, the usage information is always reported (can be viewed via `TARS Platform-> Feature Monitoring`): 442 | 443 | * memoryUsage: memory usage, which will be reported as `rss`,` heapUsed`, and `heapTotal` (in bytes) 444 | * cpuUsage: CPU usage, CPU usage will be reported, and data will be aggregated into logical single cores (unit is percentage) 445 | * eventloopLag: Event loop lag (V8 message queue delay), sample every 2 seconds (unit is millisecond) 446 | * libuv: I / O usage, will report the usage of `activeHandles` and` activeRequests` 447 | 448 | The statistical strategy of all usage information is `Avg`,` Max`, `Min` 449 | 450 | ## Nondestructive operation 451 | 452 | If your service is running on the `TARS` platform, every time a non-destructive restart or release: 453 | 454 | 1. Set the traffic status to no traffic (including routing and third-party traffic) 455 | 2. Wait for the caller to get the configuration (default is 2 minutes 13 seconds) 456 | 3. Perform the corresponding operation (restart or release) 457 | 4. Restore traffic status 458 | 459 | __Please note: If a large number of nodes perform non-destructive operations at the same time, the traffic of these nodes will be blocked at the same time, which may cause service instability. A non-destructive batch restart is recommended.__ 460 | 461 | ### Warm up 462 | 463 | During the service startup of non-destructive operation, you can choose whether to warm up: 464 | 465 | 1. After the service is started, check if all child processes are listening on the port every second (all child processes are ONLINE) 466 | 2. If the warm-up timeout period is exceeded and not all child processes are listening on the port, the non-destructive operation process fails and the user is notified (email notification) 467 | 468 | __We strongly recommend that: In any case, please complete all initialization operations before listening to the (listen) port.__ 469 | 470 | ## Architecture 471 | 472 | ![PM2](https://github.com/tars-node/node-agent/blob/master/doc/architecture.png?raw=true) 473 | 474 | When `node-agent` starts (that is, executes` cluster.fork`) service script, it does not directly load the corresponding script, but loads `node-agent / ProcessContainer.js` to wrap the service script. Then call the system's `require` to load the execution script -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | # @tars/node-agent 2 | 3 | 为了让 Node.js 应用可以运行于 TARS 框架中, `node-agent` 将作为启动器来启动应用,提供生产环境所需的服务特性。 4 | 5 | 它主要提供了如下的功能: 6 | 7 | * __内置负载均衡(通过 Cluster 模块实现)__ 8 | * __异常退出的监控与拉起__ 9 | * __日志搜集与处理__ 10 | * __支持TARS平台的管理命令__ 11 | * __支持 HTTP(s) 服务监控上报(在 TARS 平台上运行)__ 12 | * __支持服务用量上报(在 TARS 平台上运行)__ 13 | 14 | ## 安装 15 | 16 | `npm install @tars/node-agent -g` 17 | 18 | > 由于 `node-agent` 是一个 CLI 程序,所以一般需要使用 __-g__ 参数来安装 19 | 20 | ## 用法 21 | 22 | `node-agent app.js [options]` 23 | 24 | * app.js 为程序的入口脚本,详见 [入口点](#entrypoint) 节 25 | * [options] 可选配置,详见 [选项](#options) 节 26 | 27 | ## 例子 28 | 29 | 执行 app.js 文件: 30 | > $ node-agent app.js 31 | 32 | 以 `TARS` 服务的配置文件来启动: 33 | > $ node-agent app.js --config MTT.Test.conf 34 | 35 | 启动并命名应用为 MTT.Test: 36 | > $ node-agent app.js --name MTT.Test 37 | 38 | 定义日志输出路径 39 | > $ node-agent app.js --log ./logs/ 40 | 41 | 传递子进程 `node` 的启动参数: 42 | > $ node-agent app.js --node-args="--debug=7001" 43 | 44 | 定义子进程数量: 45 | > $ node-agent app.js -i 4 46 | 47 | ## 入口点 48 | 49 | `node-agent` 启动时传入的第二个参数用来指定服务脚本执行的入口点文件,其中: 50 | 51 | * 可以直接传入脚本文件用于执行,如 `./app.js` 52 | 53 | * 也可以传入脚本文件所在的目录,如 `./` 54 | 55 | 当传入的为目录时,入口点根据如下顺序进行确认: 56 | 57 | 1. 目录中存在 `package.json` 文件,则: 58 | 1. 查找 `nodeAgent.main` 59 | 2. 查找 `script.start`(此配置节需要以 `node` 打头才可识别) 60 | 3. 查找 `main` 61 | 2. 查找目录中是否存在: `server.js`、`app.js`、`start.js`、`index.js` 62 | 63 | 只要其中的一项匹配则作为入口点文件来执行,并不再往下匹配。 64 | 65 | ## 选项 66 | 67 | 68 | > Options: 69 | 70 | > -h, --help output usage information 71 | > -V, --version output the version number 72 | > -c, --config specify tars config file. NOTE: independent config will be override this 73 | > -n, --name set a for script - e.g. app.servername 74 | > -l, --log specify log file 75 | > -i, --instances launch [number] instances (for networked app)(load balanced) 76 | > --env specify environment to get specific env variables (for JSON declaration) 77 | > --http-address specify http ip:port address to pass to script - e.g. 127.0.0.1:80 78 | > --script-args space delimited arguments to pass to script - e.g. --use="https" 79 | > --node-args space delimited arguments to pass to node - e.g. --node-args="--debug=7001 --trace-deprecation" 80 | > --run-as-user The user or uid to run a managed process as 81 | > --run-as-group The group or gid to run a managed process as 82 | > --max-memory-restart specify max memory amount used to autorestart (in megaoctets) 83 | > --graceful-shutdown specify graceful shutdown timeout (in millisecond), default is 8000ms 84 | > --exception-max The program will be terminated if an exceeding max exception count, default is 5 85 | > --exception-time The program will be terminated if an exception occurs within a particular period of time, default is 5000ms 86 | > --keepalive-time specify the interval for detecting the worker which could be set to [off] if you want to debug and the default value is 60s 87 | > --applog-max-files specify max number of rolling log, default is 10 88 | > --applog-max-size specify max file size for each rolling log, use human readable unit in [K|G|M], default is 10M 89 | > --applog-level define log level, default is DEBUG 90 | > --tars-node set tars node conncetion string, agent would send notifications to tars node - e.g. tars.tarsnode.ServerObj@tcp -h 127.0.0.1 -p 10000 -t 60000 91 | > --tars-local set local interface setup string, agent would receive the notifications from tars node - e.g. tcp -h 127.0.0.1 -p 10000 -t 3000 92 | > --tars-monitor enable or disable service monitor running in tars platform, and the default value is on 93 | > --tars-monitor-http-threshold if the http(s) status code is large than the preseted threshold then this request will be considered error. default threshold is 400, set it "off" to disabled 94 | > --tars-monitor-http-seppath separate url pathname as interface name, default is on 95 | > --tars-monitor-http-socketerr considered socket error as error, default is on 96 | > --long-stack enable long stack trace to auto append asynchronous stack, default is off 97 | > --long-stack-filter-usercode filter long stack trace keep user module code only, default is off 98 | 99 | ### -c, --config 100 | 101 | 如果此服务为 TARS 服务,可在此指定服务的配置文件。 102 | 103 | 配置文件将会自动读入作为基础配置,通过设置其他的配置参数可覆盖读入的基础配置。 104 | 105 | ### -n, --name 106 | 107 | 可在此指定服务名。 108 | 109 | * 如未配置,则使用 _脚本的文件名_ 110 | * 如为 TARS 服务,则服务名必须为 _app.serverName_ 格式 111 | 112 | ### -l, --log 113 | 114 | 指定输出的日志文件根目录 115 | 116 | 如未配置,则所有日志输出采用 _stdout/stderr_ 输出 117 | 118 | ### -i, --instances 119 | 120 | `node-agent` 采用 Node.js 原生的 [Cluster](http://www.nodejs.org/api/cluster.html "Cluster") 模块来实现负载均衡。 121 | 122 | 可在此配置 `node-agent` 启动的子进程(业务进程)数量: 123 | 124 | * 未配置(或配置为 `auto`、`0`),启动的子进程数量等于 `CPU 物理核心` 个数。 125 | 126 | * 配置为 `max`,启动的子进程数量等于 CPU 个数(所有核心数)。 127 | 128 | 如果 `node-agent` 是由 `tarsnode` 启动的,会自动读取TARS配置文件中的 `tars.application.client.asyncthread` 配置节。 129 | 130 | 也可通过 `TARS平台 -> 编辑服务 -> 异步线程数` 进行调整。 131 | 132 | ### --env 133 | 134 | 设置服务启动时的 _环境变量_ , 这里需要使用 `JSON` 格式进行描述 135 | 136 | 例如:可以通过这个配置来传入当前的运行环境(开发、生产) 137 | 138 | ``` js 139 | {\"NODE_ENV\":\"production\"} 140 | ``` 141 | 142 | __请注意:当作为命令行参数传递时,这里的双引号(")需要进行转义(\")__ 143 | 144 | 如果此服务为 TARS 服务,则此参数以 `tarsnode` 可识别的方式读取并设置。 145 | 146 | ### --http-address 147 | 148 | 设定服务脚本执行所需的 `ip:port` 149 | 150 | 在脚本中可以使用环境变量 `HTTP_IP`(`IP`)、`HTTP_PORT`(`PORT`) 进行获取 151 | 152 | ``` js 153 | process.env.HTTP_IP 154 | process.env.HTTP_PORT 155 | ``` 156 | 157 | 如果此服务为 TARS 服务,则这里的值为配置文件中,第一个非TARS协议的 Servant 指明的 `ip:port` 158 | 159 | ### --script-args 160 | 161 | 设置服务脚本执行所需传入的参数 162 | 163 | 例如: 164 | 165 | > $ node-agent app.js --script-args="--use="https" 166 | 167 | 等同于 168 | 169 | > $ node app.js --use="https" 170 | 171 | ### --node-args 172 | 173 | 设置 `node cluster` 子进程所需的启动参数 174 | 175 | 例如: 176 | 177 | > $ node-agent app.js --node-args="--debug=7001 --trace-deprecation" 178 | 179 | 等同于 180 | 181 | > $ node --debug=7001 --trace-deprecation app.js 182 | 183 | ### --run-as-user, --run-as-group 184 | 185 | 指定 `node cluster` 子进程运行的用户(组) 186 | 187 | 可通过此对服务脚本进行降权执行,如未配置权限等同于 `node-agent` 启动用户(组) 188 | 189 | ### --max-memory-restart 190 | 191 | 指定服务所能使用到的最大内存。 192 | 193 | 如果子进程达到最大内存限制,将会抛出异常并退出。此 _(资源形)_ 异常 也会纳入整体的异常进行处理。 194 | 195 | ### --graceful-shutdown 196 | 197 | 正常情况下,`node-agent` 在停止服务(进程)时会通过 `worker.disconnect()` 通知服务,让服务释放资源并退出。 198 | 199 | 在这里可以设置超时时间,如果服务(进程)在给定的时间后仍然没有退出,`node-agent` 则会强制 `kill` 掉进程。 200 | 201 | 超时时间默认为 8 秒 202 | 203 | 如果 `node-agent` 是由 `tarsnode` 启动的,会自动读取TARS配置文件中的 `tars.application.server.deactivating-timeout` 配置节。 204 | 205 | ### --exception-max, --exception-time 206 | 207 | 如果(服务)子进程出现异常退出,并在一段时间内 _(--exception-time)_ 异常退出的次数没有超过最大值 _(--exception-max)_ 。`node-agent` 将会自动拉起新的(服务)子进程,否则 `node-agent` 与服务也将异常退出。 208 | 209 | 以方便第三方管理工具对服务状态进行监控 210 | 211 | --exception-time 默认值为 10s 212 | --exception-max 默认值为 2次 213 | 214 | ### --keepalive-time 215 | 216 | 如果 `node-agent` 在一段时间(--keepalive-time)内未收到(服务)子进程发送的心跳,则判定此(服务)子进程为僵尸进程(zombie process),将会直接杀死 `kill`,并作为异常进行处理。 217 | 218 | _当服务器 `可用内存` 过小时不触发此逻辑。_ 219 | 220 | __如果您想对服务脚本进行(断点)调试,这需将此设置成为 `--keepalive-time=off`__ 221 | 222 | 其默认值为 5m 223 | 224 | ### --applog-max-files, --applog-max-size, --applog-level 225 | 226 | 指定服务默认的滚动 日志大小 _(--applog-max-size)_ 、 总数 _(--applog-max-files)_ 与 日志级别 _(--applog-level)_ 。 227 | 228 | 服务的启动时会创建两份主(滚动)日志: 229 | 230 | * app.serverName.log: 所启动服务的 `stdout/stderr/console` 231 | * app.serverName_agent.log: `node-agent` 的状态信息 232 | 233 | 这个配置主要影响上面两份主(滚动)日志的输出参数 234 | 235 | 详见 [日志](#logs "logs") 节 236 | 237 | ### --tars-node, --tars-local 238 | 239 | 如果 `node-agent` 是由 `tarsnode` 启动的,则需要指定 `tarsnode` 的 RPC 连接参数 _(--tars-node)_ 与本地被调的启动参数 _(--tars-local)_。 240 | 241 | 此设置也可通过 TARS配置文件 _(--tars-config)_ 进行指定。 242 | 243 | `node-agent` 会在服务启动时向 `tarsnode` 上报服务的版本,并在服务运行过程中发送心跳包。 244 | 245 | 与此同时,`node-agent` 本地启动的(被调)服务也将从 `tarsnode` 中接收下发的消息(shutdown/message),并进行响应。 246 | 247 | ### --tars-monitor 248 | 249 | 如果您的服务是在 `TARS` 平台上运行的,`node-agent` 会自动向 `tarsstat` 上报服务的监控(用量)信息。 250 | 251 | 默认值为 on,设置为 off 可关闭自动上报功能。 252 | 253 | 具体详情可查看 `监控与用量上报` 节。 254 | 255 | ### --tars-monitor-http-threshold 256 | 257 | 如果您的服务的 HTTP(s) 返回码大于此阈值则此次请求将作为异常访问进行上报。 258 | 259 | 默认 [response.statusCode >= 400](http://www.nodejs.org/api/http.html#http_response_statuscode) 则为异常访问。 260 | 261 | 设置为 off 可关闭此特性。 262 | 263 | 具体详情可查看 `监控与用量上报` 节。 264 | 265 | ### --tars-monitor-http-seppath 266 | 267 | HTTP(s) 服务在上报时是否需要区分不同路径。 268 | 269 | 默认为区分路径,其中 url.pathname 的部分会作为服务的接口名进行上报。 270 | 271 | 如果您的服务拥有非常多(大基数)的 pathname(如 RESTful),可设置成为 off。 272 | 273 | 具体详情可查看 `监控与用量上报` 节。 274 | 275 | ### --tars-monitor-http-socketerr 276 | 277 | 在默认情况下,HTTP(s) 服务在进行上报时会将 [Socket 异常](https://nodejs.org/api/errors.html#errors_common_system_errors) 作为异常访问。 278 | 279 | 如您想关闭此特性,可设置成为 off 280 | 281 | ### --long-stack, --long-stack-filter-usercode 282 | 283 | 开启此特性后,会在异常产生时自动附加异步调用堆栈,帮助快速定位异步调用问题。 284 | 285 | 如您想过滤出用户代码(模块)所产生的堆栈,可以开启 `--long-stack-filter-usercode` 。 286 | 287 | 此特性要求 Node.js 版本需大于 v8.2.x 288 | 289 | __此特性会造成性能损耗,性能敏感代码请勿开启。__ 290 | 291 | 具体详情可查看 [LongStack](https://www.npmjs.com/package/longstack) 说明。 292 | 293 | ## 配置 294 | 295 | `node-agent` 支持以多种配置方式进行启动: 296 | 297 | * 命令行参数进行指定 298 | * 在服务脚本的 `package.json` 中指定 299 | * 在 `TARS` 服务的配置文件中指定 300 | 301 | 其中: 302 | 303 | * 在 `package.json` 或 `TARS` 配置文件中指定的值,会覆盖掉命令行参数中所指定的配置项。 304 | * 可以通过驼峰式写法将配置参数声明在 `package.json` 中 `nodeAgent` 的配置节。 305 | * 在 `TARS` 服务的配置文件中以配置参数原型直接进行声明 306 | 307 | 例如(以 nobody 用户启动子进程): 308 | 309 | 命令行参数: 310 | > node-agent app.js --run-as-user=nobody 311 | 312 | package.json: 313 | >``` js 314 | >{ 315 | > "nodeAgent" : { 316 | > "runAsUser" : "nobody" 317 | > } 318 | >} 319 | >``` 320 | 321 | TARS 配置文件: 322 | >``` xml 323 | > 324 | > 325 | > 326 | > run-as-user=nobody 327 | > 328 | > 329 | > 330 | >``` 331 | 332 | ## 消息与事件 333 | 334 | 一般情况下,用户代码无需处理(关注)进程消息与事件,但如果您想处理(响应):进程退出、TARS管理命令,则需要进行处理。 335 | 336 | ### process.on('disconnect', function) 337 | 338 | 关于此事件具体说明请参考 [Cluster Event: 'disconnect'](http://www.nodejs.org/api/cluster.html#cluster_event_disconnect) 339 | 340 | 默认情况下 `node-agent` 会对该事件进行处理,但如果用户代码监听(处理)了该事件则 `node-agent` 将不再进行处理。 341 | 342 | __请注意:您在处理完该事件后,请一定显示调用 `process.exit()` 以确保进程可以正常退出__ 343 | 344 | ### process.on('message', object) 345 | 346 | 一旦 `node-agent` 收到了来自于 `tarsnode` 的管理命令,将会通过进程消息发送给业务脚本。 347 | 348 | 传递的消息 `object` 的格式为: 349 | 350 | ```js 351 | { 352 | cmd : String, 353 | data : String 354 | } 355 | ``` 356 | 357 | 支持的消息 `cmd` 有: 358 | 359 | * tars.viewstatus : 查看服务状态 360 | * tars.setloglevel : 设置日志等级 361 | * tars.loadconfig : PUSH配置文件 362 | * tars.connection : 查看当前链接情况 363 | * 自定义命令 364 | 365 | 存在有 `data` 的 `cmd` 有: 366 | 367 | * tars.setloglevel : `INFO`、`DEBUG`、`WARN`、`ERROR`、`NONE` 368 | * tars.loadconfig : 配置文件名 369 | * 自定义命令 370 | * process.msg:[all|worker_id] : 跨进程自定义消息对象 371 | 372 | \* `node-agent` 会对 `自定义命令` 进行切分,命令中第一个空格前的字符作为 `cmd`,而后续的部分则作为 `data` 373 | 374 | ### process.send(object) 375 | 376 | 发送命令给主进程以便主进程执行特定的操作。 377 | 378 | 传递的消息 `object` 的格式与收到的消息格式相同。 379 | 380 | #### cmd = process.msg:[all|worker_id] 381 | 382 | 通过此命令,可以将自定义消息发送给参数指定的子进程。 383 | 384 | * all : 发送给所有子进程(包括自己) 385 | * worker_id : 发送给特定的子进程,其中 worker_id 为 `进程顺序 ID` (process.env.WORKER_ID) 386 | 387 | __所有消息均会通过主进程中转,在大消息量下主进程易成为性能瓶颈,请谨慎使用!__ 388 | 389 | ## 日志 390 | 391 | `node-agent` 会将服务的输出(`stdout|stderr` 管道以及 `console` 模块的输出)重定向到指定的文件(当使用 `-l --log` 参数启动时)或者管道。 392 | 393 | 日志的输出由 [winston-tars](https://github.com/tars-node/winston-tars "winston-tars") 模块实现,其输出的日志格式为:`日期 时间|PID|日志级别|文件名:行号|内容` 394 | 395 | 服务脚本可以通过 `node` 自带的 `console` 模块输出不同级别的日志。 396 | 397 | > console.info = INFO 398 | > console.log = DEBUG 399 | > console.warn = WARN 400 | > console.error = ERROR 401 | 402 | 也可通过服务的 `stdout|stderr` 管道输出。 403 | 404 | > process.stdout = INFO 405 | > process.stderr = ERROR 406 | 407 | 日志级别的优先级为: `INFO` < `DEBUG` < `WARN` < `ERROR` < `NONE` 408 | 409 | 其中,默认的日志级别为:`DEBUG` 410 | 411 | ## 环境变量 412 | 413 | `node-agent` 通过环境变量向服务脚本提供所需的变量: 414 | 415 | * `process.env.IP`:HTTP(s) 可监听的 IP。 416 | * `process.env.PORT`:HTTP(s) 可监听的端口。 417 | * `process.env.WORKER_ID` 进程顺序 ID(例如启动 8 个进程,第一个为0,第二个为1,以此类推),重新启动的进程仍然使用之前的 ID。 418 | 419 | 如服务是由 `tarsnode` 启动的,还支持如下变量: 420 | 421 | * `process.env.TARS_CONFIG`:启动服务所使用的TARS配置文件所在的绝对路径。 422 | * `process.env.TARS_MONITOR`:是否开启监控(特性)上报(统计)。 423 | 424 | __请注意:环境变量全为 String 类型__ 425 | 426 | ## 监控与用量上报 427 | 428 | 如果您的服务是在 `TARS` 平台上运行的,`node-agent` 会自动向 `tarsstat` 上报服务的监控(用量)信息。 429 | 430 | ### 监控信息 431 | 432 | 监控信息的上报与您启动的服务及其调用者有关(可通过 `TARS平台 -> 服务监控` 查看): 433 | 434 | * HTTP(s) 435 | * 服务端:[response.statusCode >= 400](http://www.nodejs.org/api/http.html#http_response_statuscode) 为失败,所有请求的超时为 0 436 | * 可通过 [--tars-monitor-http-threshold](#tars-monitor-http-threshold) 与 [--tars-monitor-http-seppath](#tars-monitor-http-seppath) 进行配置 437 | 438 | 更多详情您可访问 [@tars/monitor.stat](https://github.com/tars-node/monitor) 获取。 439 | 440 | ### 用量信息 441 | 442 | 无论您启动的服务是什么类型,用量信息总是上报(可通过 `TARS平台 -> 特性监控` 查看): 443 | 444 | * memoryUsage: 内存用量,将会上报 `rss`、`heapUsed`、`heapTotal` 这三个用量(单位为字节) 445 | * cpuUsage: CPU用量,将会上报CPU使用率,数据汇总为逻辑单核(单位为百分比) 446 | * eventloopLag: 事件循环滞后(V8消息队列延迟),每隔2秒采样(单位为毫秒) 447 | * libuv: I/O用量,将会上报 `activeHandles`、`activeRequests` 这两个用量 448 | 449 | 所有的用量信息的统计策略均为 `Avg`、`Max`、`Min` 450 | 451 | ## 无损操作 452 | 453 | 如果您的服务是在 `TARS` 平台上运行的,每次无损重启或发布时: 454 | 455 | 1. 设置流量状态为无流量(包括路由和第三方流量) 456 | 2. 等待调用方获取配置(默认为 2分13秒) 457 | 3. 执行对应操作(重启或发布) 458 | 4. 恢复流量状态 459 | 460 | __请注意:如果大量节点同时进行无损操作,会同时屏蔽这些节点的流量,可能会造成服务不稳定。建议采用无损分批重启。__ 461 | 462 | ### 预热 463 | 464 | 在无损操作的服务启动过程中,可以选择是否需要进行预热: 465 | 466 | 1. 服务启动后每秒检查是否所有子进程都监听了端口(所有子进程状态均为 ONLINE) 467 | 2. 如果超过预热超时时间,且并非所有子进程都监听了端口,则无损操作流程失败并通知用户(邮件通知) 468 | 469 | __我们强烈建议您:在任何情况下,请完成所有初始化操作后再监听(listen)端口。__ 470 | 471 | ## 架构 472 | 473 | ![PM2](https://github.com/tars-node/node-agent/blob/master/doc/architecture.png?raw=true) 474 | 475 | `node-agent` 在启动(也就是执行 `cluster.fork`)服务脚本时,并不会直接载入对应脚本,而是载入 `node-agent/ProcessContainer.js` 来对服务脚本进行包装,之后再调用系统的 `require` 载入执行脚本 -------------------------------------------------------------------------------- /bin/node-agent: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Tencent is pleased to support the open source community by making Tars available. 5 | * 6 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 7 | * 8 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 9 | * in compliance with the License. You may obtain a copy of the License at 10 | * 11 | * https://opensource.org/licenses/BSD-3-Clause 12 | * 13 | * Unless required by applicable law or agreed to in writing, software distributed 14 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 15 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | 'use strict'; 20 | 21 | var path = require('path'); 22 | var util = require('util'); 23 | var fs = require('fs'); 24 | 25 | var chalk = require('chalk'); 26 | var commander = require('commander'); 27 | var pkg = require('../package.json'); 28 | 29 | var constants = require('../lib/constants'); 30 | var convert = require('../lib/util/convert'); 31 | var CLI = require('../lib/CLI'); 32 | 33 | var TarsConfigure = require('@tars/utils').Config; 34 | 35 | var split = function(val) { 36 | return val.split(' '); 37 | }; 38 | 39 | var jsonParse = function(val) { 40 | try { 41 | return JSON.parse(val); 42 | } catch(e) { 43 | return ''; 44 | } 45 | }; 46 | 47 | var aliveParse = function(val) { 48 | if (val === 'off') { 49 | return 0; 50 | } 51 | return parseInt(val); 52 | }; 53 | 54 | var instancesParse = function(val) { 55 | if (val === 'max') { 56 | return -1; 57 | } 58 | 59 | if (val === 'auto') { 60 | return 0; 61 | } 62 | 63 | val = parseInt(val); 64 | if (isNaN(val) || val < 0) { 65 | return 0; 66 | } else { 67 | return val; 68 | } 69 | }; 70 | 71 | var toBoolean = function(val) { 72 | if (typeof val === 'string') { 73 | return val === 'on'; 74 | } else { 75 | return false; 76 | } 77 | }; 78 | 79 | var existsSync = function(path) { 80 | try { 81 | fs.statSync(path); 82 | return true; 83 | } catch (e) { 84 | return false; 85 | } 86 | }; 87 | 88 | var parseConfig = function(script, file) { 89 | var TarsConfig = new TarsConfigure(), 90 | opts = { 91 | config : file 92 | }, serverObj; 93 | 94 | TarsConfig.parseFile(file); 95 | 96 | serverObj = TarsConfig.get('tars.application', {}); 97 | 98 | ['run-as-user', 99 | 'run-as-group', 100 | { 101 | org : 'asyncthread', 102 | dest : 'instances', 103 | coercion : instancesParse, 104 | domain : 'client' 105 | }, { 106 | org : 'instances', 107 | coercion : instancesParse 108 | }, { 109 | org : 'script-args', 110 | coercion : split 111 | }, { 112 | org : 'node-args', 113 | coercion : split 114 | }, { 115 | org : 'max-memory-restart', 116 | coercion : parseInt 117 | }, { 118 | org : 'deactivating-timeout', 119 | dest : 'graceful-shutdown', 120 | coercion : parseInt 121 | }, { 122 | org : 'exception-max', 123 | coercion : parseInt 124 | }, { 125 | org : 'exception-time', 126 | coercion : parseInt 127 | }, { 128 | org : 'keepalive-time', 129 | coercion : aliveParse 130 | }, { 131 | org : 'logpath', 132 | dest : 'log' 133 | }, { 134 | org : 'log-files', 135 | dest : 'applog-max-files', 136 | coercion : parseInt 137 | }, { 138 | org : 'logsize', 139 | dest : 'applog-max-size', 140 | coercion : convert.friendlyformat 141 | }, { 142 | org : 'logLevel', 143 | dest : 'applog-level' 144 | }, { 145 | org : 'node', 146 | dest : 'tars-node' 147 | }, { 148 | org : 'local', 149 | dest : 'tars-local' 150 | }, { 151 | org : 'tars-monitor', 152 | coercion : toBoolean 153 | }, { 154 | org : 'tars-monitor-http-threshold', 155 | coercion : aliveParse 156 | }, { 157 | org : 'tars-monitor-http-seppath', 158 | coercion : toBoolean 159 | }, { 160 | org : 'tars-monitor-http-socketerr', 161 | coercion : toBoolean 162 | }, { 163 | org : 'long-stack', 164 | coercion : toBoolean 165 | }, { 166 | org : 'long-stack-filter-usercode', 167 | coercion : toBoolean 168 | }].forEach(function(item) { 169 | var value = (serverObj[item.domain || 'server'] || {})[item.org || item]; 170 | if (value) { 171 | opts[convert.camelcase(item.dest || item.org || item)] = item.coercion ? item.coercion(value) : value; 172 | } 173 | }); 174 | 175 | opts['name'] = TarsConfig.get('tars.application.server.app', constants.TARS_DEFAULT_APP) + '.' + TarsConfig.get('tars.application.server.server', path.basename(script, path.extname(script))); 176 | 177 | if (serverObj['server']) { 178 | Object.getOwnPropertyNames(serverObj['server']).some(function(key) { 179 | if (serverObj['server'][key] && typeof serverObj['server'][key] === 'object' && serverObj['server'][key].protocol === 'not_tars') { 180 | opts[convert.camelcase('http-address')] = serverObj['server'][key].endpoint.match(/\-h\s([^\s]+)/)[1] + ':' + serverObj['server'][key].endpoint.match(/\-p\s([^\s]+)/)[1]; 181 | return true; 182 | } 183 | return false; 184 | }); 185 | } 186 | 187 | return opts; 188 | }; 189 | 190 | var parsePackage = function(file) { 191 | if (!scriptPkg) { 192 | scriptPkg = require(file); 193 | } 194 | 195 | scriptPkg.nodeAgent = scriptPkg.nodeAgent || {}; 196 | 197 | if (scriptPkg.name) { 198 | scriptPkg.nodeAgent.name = scriptPkg.name; 199 | } 200 | 201 | return scriptPkg.nodeAgent; 202 | }; 203 | 204 | var error = function(str, code) { 205 | console.error(chalk.red(str)); 206 | process.exit(code || 0); 207 | }; 208 | 209 | commander 210 | .version(pkg.version) 211 | .option('-c, --config ', 'specify tars config file. NOTE: independent config will be override this') 212 | .option('-n, --name ', 'set a for script - e.g. app.servername') 213 | .option('-l, --log ', 'specify log file') 214 | .option('-i, --instances ', 'launch [number] instances (for networked app)(load balanced)', instancesParse) 215 | .option('--env ', 'specify environment to get specific env variables (for JSON declaration)', jsonParse) 216 | .option('--http-address ', 'specify http ip:port address to pass to script - e.g. 127.0.0.1:80') 217 | .option('--script-args ', "space delimited arguments to pass to script - e.g. --use=\"https\"", split) 218 | .option('--node-args ', "space delimited arguments to pass to node - e.g. --node-args=\"--debug=7001 --trace-deprecation\"", split) 219 | .option('--run-as-user ', 'The user or uid to run a managed process as') 220 | .option('--run-as-group ', 'The group or gid to run a managed process as') 221 | .option('--max-memory-restart ', 'specify max memory amount used to autorestart (in megaoctets)', parseInt) 222 | .option('--graceful-shutdown ', util.format('specify graceful shutdown timeout (in millisecond), default is %sms', constants.GRACEFUL_TIMEOUT), parseInt) 223 | .option('--exception-max ', util.format('The program will be terminated if an exceeding max exception count, default is %s', constants.EXCEPTION_TOTAL), parseInt) 224 | .option('--exception-time ', util.format('The program will be terminated if an exception occurs within a particular period of time, default is %sms', constants.EXCEPTION_TIME), parseInt) 225 | .option('--keepalive-time ', util.format('specify the interval for detecting the worker which could be set to [off] if you want to debug and the default value is %ss', constants.WORKER_DETECT_INTERVAL), aliveParse) 226 | .option('--applog-max-files ', util.format('specify max number of rolling log, default is %s', constants.APPLOG_MAX_FILES), parseInt) 227 | .option('--applog-max-size ', util.format('specify max file size for each rolling log, use human readable unit in [K|G|M], default is %s', convert.byteformat(constants.APPLOG_MAX_SIZE)), convert.friendlyformat) 228 | .option('--applog-level ', util.format('define log level, default is %s', constants.APPLOG_LEVEL)) 229 | .option('--tars-node ', 'set tars node conncetion string, agent would send notifications to tars node - e.g. tars.tarsnode.ServerObj@tcp -h 127.0.0.1 -p 10000 -t 60000') 230 | .option('--tars-local ', 'set local interface setup string, agent would receive the notifications from tars node - e.g. tcp -h 127.0.0.1 -p 10000 -t 3000') 231 | .option('--tars-monitor ', util.format('enable or disable service monitor running in tars platform, and the default value is %s', constants.TARS_MONITOR ? 'on' : 'off'), toBoolean) 232 | .option('--tars-monitor-http-threshold ', util.format('if the http(s) status code is large than the preseted threshold then this request will be considered error. default threshold is %s, set it "off" to disabled', constants.TARS_MONITOR_HTTP_THRESHOLD), aliveParse) 233 | .option('--tars-monitor-http-seppath ', util.format('separate url pathname as interface name, default is %s', constants.TARS_MONITOR_HTTP_SEPPATH ? 'on' : 'off'), toBoolean) 234 | .option('--tars-monitor-http-socketerr ', util.format('considered socket error as error, default is %s', constants.TARS_MONITOR_HTTP_SOCKETERR ? 'on' : 'off'), toBoolean) 235 | .option('--long-stack ', util.format('enable long stack trace to auto append asynchronous stack, default is %s', constants.LONG_STACK ? 'on' : 'off'), toBoolean) 236 | .option('--long-stack-filter-usercode ', util.format('filter long stack trace keep user module code only, default is %s', constants.LONG_STACK_FILTER_USERCODE ? 'on' : 'off'), toBoolean) 237 | .usage('app.js [options]') 238 | .parse(process.argv); 239 | 240 | if (process.argv.length == 2) { 241 | commander.parse(process.argv); 242 | commander.outputHelp(); 243 | process.exit(0); 244 | } 245 | 246 | if (!commander.args[0]) { 247 | error('>> The script is NOT specified.'); 248 | return; 249 | } 250 | 251 | var scriptStat, scriptPkg, entrypoint, configOpt; 252 | 253 | try { 254 | scriptStat = fs.statSync(commander.args[0]); 255 | } catch(e) { 256 | error('>> Can NOT find the script or directory'); 257 | return; 258 | } 259 | 260 | if (scriptStat.isFile() && path.extname(commander.args[0]).toLowerCase() === '.js') { 261 | entrypoint = path.resolve(commander.args[0]); 262 | } else if (scriptStat.isDirectory()) { 263 | if (existsSync(path.resolve(commander.args[0], 'package.json'))) { 264 | try { 265 | scriptPkg = require(path.resolve(commander.args[0], 'package.json')); 266 | } catch(e) {} 267 | 268 | if (scriptPkg && scriptPkg.nodeAgent && scriptPkg.nodeAgent.main) { 269 | entrypoint = path.resolve(commander.args[0], scriptPkg.nodeAgent.main); 270 | } else if (scriptPkg && scriptPkg.scripts && scriptPkg.scripts.start && scriptPkg.scripts.start.indexOf('node ') === 0) { 271 | entrypoint = path.resolve(commander.args[0], scriptPkg.scripts.start.slice(5)); 272 | } else if (scriptPkg && scriptPkg.main) { 273 | entrypoint = path.resolve(commander.args[0], scriptPkg.main); 274 | } 275 | } 276 | 277 | if (!entrypoint) { 278 | constants.ENTRY_POINT_NAME.some(function(name) { 279 | if (existsSync(path.resolve(commander.args[0], name))) { 280 | entrypoint = path.resolve(commander.args[0], name); 281 | return true; 282 | } else { 283 | return false; 284 | } 285 | }); 286 | } 287 | 288 | if (!entrypoint) { 289 | error('>> Can NOT find the entry point in the directory'); 290 | return; 291 | } 292 | } else { 293 | error('>> The script is neither a file nor a directory'); 294 | return; 295 | } 296 | 297 | if (!existsSync(entrypoint)) { 298 | error(util.format('>> Can NOT find the entrypoint(%s) in the file system', entrypoint)); 299 | return; 300 | } 301 | 302 | if (commander.config) { 303 | try { 304 | process.env.TARS_CONFIG = commander.config 305 | configOpt = parseConfig(commander.args[0], commander.config); 306 | } catch(e) { 307 | error(chalk.red('>> Reading the config file failed.')); 308 | return; 309 | } 310 | } else if (existsSync(path.join(path.dirname(entrypoint), 'package.json'))) { 311 | try { 312 | configOpt = parsePackage(path.join(path.dirname(entrypoint), 'package.json')); 313 | } catch(e) {} 314 | } 315 | 316 | if (configOpt) { 317 | Object.getOwnPropertyNames(configOpt).forEach(function(key) { 318 | if (!commander[key] || typeof commander[key] === 'function') { 319 | commander[key] = configOpt[key]; 320 | } 321 | }); 322 | } 323 | 324 | CLI.start(entrypoint, commander); -------------------------------------------------------------------------------- /doc/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tars-node/node-agent/32a47d188e17b63aed78ba1cd88cd7a58edd9924/doc/architecture.png -------------------------------------------------------------------------------- /lib/CLI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var path = require('path'); 20 | var util = require('util'); 21 | var cluster = require('cluster'); 22 | var os = require('os'); 23 | 24 | var compareVersions = require('compare-versions'); 25 | 26 | var God = require('./God'); 27 | var constants = require('./constants'); 28 | var convert = require('./util/convert'); 29 | var cpu = require('./util/cpu'); 30 | var deps = require('./util/lsdeps'); 31 | var Log = require('./log'); 32 | var pkg = require('../package.json'); 33 | 34 | var tarsReport = require('./tars/Report'); 35 | var tarsMessage = require('./tars/Message'); 36 | var tarsNotify = require('./tars/Notify'); 37 | 38 | var bindEvents = function() { 39 | var exception = false; 40 | 41 | cluster.on('fork', function(worker) { 42 | console.info('worker(%s), forked.', worker.process.pid); 43 | }).on('online', function(worker) { 44 | console.info('worker(%s), online.', worker.process.pid); 45 | }).on('listening', function(worker, address) { 46 | console.info('worker(%s), listening on %s:%s', worker.process.pid, address.address || '', address.port); 47 | }).on('fork', function(worker) { 48 | var procStd = function(pid, level) { 49 | return function(buf) { 50 | buf.toString().split('\n').forEach(function(line) { 51 | if (line.length > 0) { 52 | if (line[line.length - 1] === '\r') { 53 | line = line.slice(0, -1); 54 | } 55 | 56 | Log.append(null, { 57 | level : level, 58 | msg : line, 59 | meta : { 60 | pid : pid 61 | } 62 | }); 63 | } 64 | }); 65 | }; 66 | }; 67 | 68 | if (Log.isLogToFile()) { 69 | worker.process.stdout.on('data', procStd(worker.process.pid, 'info')); 70 | worker.process.stderr.on('data', procStd(worker.process.pid, 'error')); 71 | } else { 72 | worker.process.stdout.pipe(process.stdout); 73 | worker.process.stderr.pipe(process.stderr); 74 | } 75 | }); 76 | 77 | God.events.on('message', function(code, worker, args) { 78 | switch(code) { 79 | case constants.GOD_MESSAGE.EXCEPTION_REACHED_COND : { 80 | console.error('exception occurred more than %s times within %s seconds, exiting ...', constants.EXCEPTION_TOTAL, constants.EXCEPTION_TIME / 1000); 81 | tarsNotify.report.error(util.format('exiting,exception occurred more than %s times within %s seconds', constants.EXCEPTION_TOTAL, constants.EXCEPTION_TIME / 1000), ''); 82 | exception = true; 83 | break; 84 | } 85 | case constants.GOD_MESSAGE.KILLING_ALL_WORKERS : { 86 | console.info('killing all worker process ...'); 87 | tarsReport.destroy(); 88 | tarsMessage.destroy(); 89 | Log.close(); 90 | break; 91 | } 92 | case constants.GOD_MESSAGE.KILLING_WORKER : { 93 | console.info('killing worker(%s) ...', worker.process.pid); 94 | break; 95 | } 96 | case constants.GOD_MESSAGE.FORCE_KILL_WORKER : { 97 | console.error('exceeded the graceful timeout, force kill worker(%s) ...', worker.process.pid); 98 | tarsNotify.report.error('exceeded the graceful timeout, force kill worker', worker.process.pid); 99 | break; 100 | } 101 | case constants.GOD_MESSAGE.ALL_WORKERS_STOPPED : { 102 | console.info('all workers killed, really exiting now ...'); 103 | setTimeout(function() { 104 | process.exit(exception ? constants.CODE_UNCAUGHTEXCEPTION : 0); 105 | }, 100).unref(); 106 | break; 107 | } 108 | case constants.GOD_MESSAGE.STOP_ZOMBIE_WORKER : { 109 | console.error('detected zombie worker(%s).', worker.process.pid); 110 | tarsNotify.report.error('detected zombie worker.', worker.process.pid); 111 | break; 112 | } 113 | case constants.GOD_MESSAGE.KILL_ERROR : { 114 | console.error('kill worker(%s) failed, %s.', worker.process.pid, args || 'no error'); 115 | tarsNotify.report.error('kill worker failed', worker.process.pid); 116 | break; 117 | } 118 | } 119 | }).on('exit', function(worker, error, code, signal) { 120 | if (error) { 121 | console.error('worker(%s), exit unexpected.', worker.process.pid); 122 | tarsNotify.report.error('worker exit unexpected', worker.process.pid); 123 | 124 | if (typeof error === 'string') { 125 | Log.append(null, { 126 | level : 'error', 127 | msg : error, 128 | meta : { 129 | pid : worker.process.pid 130 | } 131 | }); 132 | } 133 | } else { 134 | console.info('worker(%s), exit normally%s.', worker.process.pid, convert.friendlyExit(code, signal, ' with')); 135 | } 136 | }); 137 | 138 | process.once('SIGINT', function() { 139 | console.info('received kill or Ctrl-C signal.'); 140 | tarsNotify.report.info('stop'); 141 | God.killAll(); 142 | }).on('exit', function(code) { 143 | console.info('exit%s.', convert.friendlyExit(code, null, ' with')); 144 | }); 145 | 146 | tarsMessage.on('notify', function(command, data, callback) { 147 | var mesgObj = { 148 | cmd : command 149 | }; 150 | 151 | if (data) { 152 | mesgObj.data = data; 153 | } 154 | 155 | // send to worker 156 | God.send(mesgObj); 157 | 158 | // send to master(itself) 159 | mesgObj.setRet = callback; 160 | process.emit('message', mesgObj); 161 | }).on('shutdown', function() { 162 | console.info('received TARS shutdown signal.'); 163 | tarsNotify.report.info('stop'); 164 | God.killAll(); 165 | }); 166 | 167 | process.on('message', function(message) { 168 | if (message) { 169 | switch (message.cmd) { 170 | case 'tars.setloglevel' : { 171 | Log.setLevel(message.data, null); 172 | break; 173 | } 174 | case 'preheatCheck' : { 175 | if (God.getStatus().every(function (status) { 176 | return status === constants.WORKER_STATUS.ONLINE; 177 | })) { 178 | message.setRet('success'); 179 | } else { 180 | message.setRet('not ready'); 181 | } 182 | break; 183 | } 184 | case 'tars.viewversion' : { 185 | message.setRet(pkg.version); 186 | break; 187 | } 188 | } 189 | } 190 | }); 191 | }; 192 | 193 | var initLog = function(name, dir) { 194 | Log.prepare(name, dir); 195 | 196 | Log.init(null, 'TarsRotate', { 197 | maxFiles : constants.APPLOG_MAX_FILES, 198 | maxSize : constants.APPLOG_MAX_SIZE 199 | }); 200 | 201 | Log.init('agent', 'TarsRotate', { 202 | maxFiles : constants.APPLOG_MAX_FILES, 203 | maxSize : constants.APPLOG_MAX_SIZE 204 | }); 205 | 206 | Log.setLevel(constants.APPLOG_LEVEL, null); 207 | }; 208 | 209 | var outRedirect = function() { 210 | var register = function(level) { 211 | return function() { 212 | Log.append('agent', { 213 | level : level, 214 | msg : util.format.apply(util, arguments), 215 | meta : { 216 | pid : process.pid 217 | } 218 | }); 219 | }; 220 | }; 221 | 222 | console.info = register('info'); 223 | console.warn = register('warn'); 224 | console.error = register('error'); 225 | }; 226 | 227 | var getWorkerArgs = function(script, opts) { 228 | var args = {}, obj; 229 | 230 | args['script'] = script; 231 | 232 | if (opts.scriptArgs) { 233 | args['script_args'] = opts.scriptArgs; 234 | } 235 | 236 | if (opts.nodeArgs) { 237 | args['node_args'] = opts.nodeArgs; 238 | } 239 | 240 | //worker process title 241 | args['name'] = typeof opts.name === 'string' ? opts.name : path.basename(script, path.extname(script)); 242 | 243 | //pass custom env to worker 244 | if (opts.env) { 245 | args['env'] = opts.env; 246 | } 247 | 248 | if (opts.httpAddress) { 249 | obj = convert.extractAddress(opts.httpAddress); 250 | if (obj) { 251 | args['http_ip'] = obj.ip; 252 | args['http_port'] = obj.port; 253 | } 254 | } 255 | 256 | //specify worker uid and gid, if not set it's equal to Master 257 | if (opts.runAsUser) { 258 | args['run_as_user'] = opts.runAsUser; 259 | } 260 | if (opts.runAsGroup) { 261 | args['run_as_group'] = opts.runAsGroup; 262 | } 263 | 264 | if (!isNaN(opts.maxMemoryRestart)) { 265 | if (!Array.isArray(args['node_args'])) { 266 | args['node_args'] = []; 267 | } 268 | args['node_args'].push('--max-old-space-size=' + opts.maxMemoryRestart); 269 | } 270 | 271 | if (opts.config) { 272 | args['config'] = opts.config; 273 | } 274 | 275 | if (!isNaN(opts.keepaliveTime)) { 276 | args['keepaliveTime'] = opts.keepaliveTime; 277 | } 278 | 279 | if (typeof opts.tarsMonitor === 'boolean') { 280 | args['tarsMonitor'] = opts.tarsMonitor; 281 | } 282 | 283 | if (opts.log) { 284 | args['log'] = path.join(opts.log, args['name'].replace('.', '/') + '/'); 285 | } 286 | 287 | return args; 288 | }; 289 | 290 | var setConstants = function(opts) { 291 | if (!isNaN(opts.gracefulShutdown)) { 292 | constants.GRACEFUL_TIMEOUT = opts.gracefulShutdown; 293 | } 294 | if (opts.config) { 295 | if (constants.GRACEFUL_TIMEOUT > 1000) { 296 | constants.GRACEFUL_TIMEOUT -= 1000; 297 | } else { 298 | constants.GRACEFUL_TIMEOUT = 0; 299 | } 300 | } 301 | 302 | if (!isNaN(opts.exceptionMax)) { 303 | constants.EXCEPTION_TOTAL = opts.exceptionMax; 304 | } 305 | 306 | if (!isNaN(opts.exceptionTime)) { 307 | constants.EXCEPTION_TIME = opts.exceptionTime; 308 | } 309 | 310 | if (!isNaN(opts.keepaliveTime)) { 311 | constants.WORKER_DETECT_INTERVAL = opts.keepaliveTime; 312 | } 313 | 314 | if (!isNaN(opts.applogMaxFiles)) { 315 | constants.APPLOG_MAX_FILES = opts.applogMaxFiles; 316 | } 317 | 318 | if (!isNaN(opts.applogMaxSize)) { 319 | constants.APPLOG_MAX_SIZE = opts.applogMaxSize; 320 | } 321 | 322 | if (opts.applogLevel) { 323 | constants.APPLOG_LEVEL = opts.applogLevel; 324 | } 325 | 326 | if (typeof opts.tarsMonitor === 'boolean') { 327 | constants.TARS_MONITOR = opts.tarsMonitor; 328 | } 329 | 330 | if (!isNaN(opts.tarsMonitorHttpThreshold)) { 331 | constants.TARS_MONITOR_HTTP_THRESHOLD = opts.tarsMonitorHttpThreshold; 332 | } 333 | 334 | if (typeof opts.tarsMonitorHttpSeppath === 'boolean') { 335 | constants.TARS_MONITOR_HTTP_SEPPATH = opts.tarsMonitorHttpSeppath; 336 | } 337 | 338 | if (typeof opts.tarsMonitorHttpSocketerr === 'boolean') { 339 | constants.TARS_MONITOR_HTTP_SOCKETERR = opts.tarsMonitorHttpSocketerr; 340 | } 341 | 342 | if (typeof opts.longStack === 'boolean') { 343 | constants.LONG_STACK = opts.longStack; 344 | } 345 | 346 | if (typeof opts.longStackFilterUsercode === 'boolean') { 347 | constants.LONG_STACK_FILTER_USERCODE = opts.longStackFilterUsercode; 348 | } 349 | 350 | if (constants.LONG_STACK && compareVersions(process.versions.node, '8.2.0') < 0) { 351 | constants.LONG_STACK = false; 352 | } 353 | 354 | if (!constants.LONG_STACK) { 355 | constants.LONG_STACK_FILTER_USERCODE = false; 356 | } 357 | }; 358 | 359 | var initTarsComponent = function(args, opts) { 360 | if (opts.tarsNode) { 361 | console.info('tars node:', opts.tarsNode); 362 | tarsReport.init(args['name'], opts.tarsNode, opts.config); 363 | tarsReport.reportVersion(pkg.version || process.version); 364 | tarsReport.keepAlive(); 365 | } 366 | 367 | if (opts.tarsLocal) { 368 | console.info('local interface:', opts.tarsLocal); 369 | tarsMessage.startServer(args['name'], opts.tarsLocal); 370 | } 371 | 372 | if (opts.config) { 373 | tarsNotify.init(opts.config); 374 | } 375 | }; 376 | 377 | var startWorker = function(opts) { 378 | var instances; 379 | 380 | if (!isNaN(opts.instances) && opts.instances > 0) { 381 | instances = opts.instances; 382 | } else { 383 | if (opts.instances === -1) { // instances = max 384 | instances = cpu.totalCores; 385 | } else { // instances = auto 386 | if (cpu.physicalCores > 0 && cpu.totalCores > cpu.physicalCores) { //physicalCores correct 387 | instances = cpu.physicalCores; 388 | } else { 389 | instances = cpu.totalCores; 390 | } 391 | } 392 | } 393 | 394 | instances = instances || 1; 395 | 396 | console.info('forking %s workers ...', instances); 397 | 398 | God.startWorker(instances); 399 | }; 400 | 401 | var deviceInfo = function() { 402 | if (cpu.physicalCores !== 0) { 403 | return util.format('%s arch, %d cpus, %d physical cores, %s platform, %s', os.arch(), cpu.totalCores, cpu.physicalCores, os.platform(), os.hostname()); 404 | } else { 405 | return util.format('%s arch, %d cpus, %s platform, %s', os.arch(), cpu.totalCores, os.platform(), os.hostname()); 406 | } 407 | }; 408 | 409 | exports.start = function(script, opts) { 410 | var args = getWorkerArgs(script, opts); 411 | 412 | setConstants(opts); 413 | 414 | process.title = util.format('%s: master process', path.resolve(process.cwd(), script)); 415 | 416 | initLog(args['name'], args['log']); 417 | outRedirect(); 418 | 419 | console.info('starting agent ...'); 420 | console.info('node:', process.version); 421 | console.info('version:', 'v' + pkg.version); 422 | 423 | deps.list(function(err, depslist) { 424 | if (!err) { 425 | console.info('dependencies:', depslist); 426 | } 427 | 428 | console.info('options:', util.inspect(args).replace(/[\n|\r]/g, '')); 429 | 430 | cpu.init(function(err) { 431 | if (err) { 432 | console.warn('%s, fallback to use os.cpus()', err); 433 | } 434 | 435 | console.info('device:', deviceInfo()); 436 | 437 | bindEvents(); 438 | God.prepare(args); 439 | 440 | initTarsComponent(args, opts); 441 | 442 | startWorker(opts); 443 | 444 | tarsNotify.report.info('restart'); 445 | }); 446 | }); 447 | }; -------------------------------------------------------------------------------- /lib/God.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var cluster = require('cluster'); 20 | var path = require('path'); 21 | 22 | var treekill = require('./util/treekill'); 23 | 24 | var constants = require('./constants'); 25 | 26 | var EventEmitter = new require('events').EventEmitter; 27 | var events = new EventEmitter(); 28 | 29 | var exception_count = 0, workers_seq = []; 30 | var exception_timer, heartbeat_timer; 31 | 32 | var env = {}; 33 | 34 | var allWorkers = function() { 35 | return Object.getOwnPropertyNames(cluster.workers).map(function(id) { 36 | return cluster.workers[id]; 37 | }); 38 | }; 39 | 40 | var setCluster = function(args, execArgv) { 41 | var settings = { 42 | exec : path.resolve(path.dirname(module.filename), 'ProcessContainer.js'), 43 | silent : true 44 | }; 45 | if (args) { 46 | settings.args = args; 47 | } 48 | if (execArgv) { 49 | settings.execArgv = execArgv; 50 | } 51 | 52 | cluster.setupMaster(settings); 53 | 54 | cluster.on('exit', function(worker, code, signal) { 55 | var error = false; 56 | 57 | if (worker._hasError || code === constants.CODE_UNCAUGHTEXCEPTION) { 58 | error = true; 59 | } 60 | 61 | if (worker._errMesg) { 62 | error = worker._errMesg; 63 | } 64 | 65 | events.emit('exit', worker, error, code, signal); 66 | }).on('exit', function(worker) { 67 | var exitedAfterDisconnect = typeof worker.exitedAfterDisconnect === 'boolean' ? worker.exitedAfterDisconnect : worker.suicide; 68 | 69 | workers_seq[worker._seq] = false; 70 | 71 | worker._status = constants.WORKER_STATUS.STOPPED; 72 | 73 | if (worker._timerId) { 74 | clearTimeout(worker._timerId); 75 | delete worker._timerId; 76 | } 77 | 78 | if (!exitedAfterDisconnect || worker._hasError) { 79 | switch(canStartWorker()) { 80 | case constants.CAN_START_WORKER.OK : { 81 | startWorker(); 82 | break; 83 | } 84 | case constants.CAN_START_WORKER.NEED_TO_KILLALL : { 85 | killAll(); 86 | break; 87 | } 88 | } 89 | } 90 | 91 | if (workers_seq.every(function (exists) { 92 | return exists !== true; 93 | })) { 94 | destroy(); 95 | } 96 | }).on('online', function(worker) { 97 | worker._status = constants.WORKER_STATUS.ONLINE; 98 | }).on('worker_message', function(worker, message) { 99 | var cmd = message.cmd, data = message.data, seq = null; 100 | 101 | if (typeof cmd !== 'string') { 102 | return; 103 | } 104 | 105 | if (cmd.indexOf('process.msg:') === 0) { 106 | seq = cmd.slice(12); 107 | if (seq === 'all') { 108 | allWorkers().forEach(function(worker) { 109 | worker.send(message); 110 | }); 111 | } else { 112 | seq = parseInt(seq); 113 | if (!isNaN(seq)) { 114 | allWorkers().forEach(function(worker) { 115 | if (worker._seq === seq) { 116 | worker.send(message); 117 | } 118 | }); 119 | } 120 | } 121 | return; 122 | } 123 | 124 | switch(cmd) { 125 | case 'god:err' : { 126 | worker._hasError = true; 127 | worker._errMesg = data; 128 | return; 129 | } 130 | case 'god:alive' : { 131 | worker._heartbeat = process.uptime(); 132 | return; 133 | } 134 | } 135 | }); 136 | }; 137 | 138 | var setArgs = function(args) { 139 | if (args['env'] && typeof args['env'] === 'object') { 140 | env = args['env']; 141 | } 142 | 143 | env['agent_args'] = {}; 144 | 145 | /* 146 | * Private env - Main Script 147 | */ 148 | 149 | env['agent_args']['exec_script'] = path.resolve(process.cwd(), args['script']); 150 | 151 | /* 152 | * Private env - Process Privilege 153 | */ 154 | 155 | if (args['run_as_user']) { 156 | env['agent_args']['process_user'] = args['run_as_user']; 157 | } 158 | 159 | if (args['run_as_group']) { 160 | env['agent_args']['process_group'] = args['run_as_group']; 161 | } 162 | 163 | /* 164 | * Private env - Heartbeat 165 | */ 166 | 167 | if (typeof args['keepaliveTime'] === 'number' && !isNaN(args['keepaliveTime'])) { 168 | env['agent_args']['process_keepalive'] = args['keepaliveTime']; 169 | } 170 | 171 | /* 172 | * Private env - Logger Settings 173 | */ 174 | 175 | if (args['log'] && args['name']) { 176 | env['agent_args']['log_main'] = path.join(args['log'], args['name'] + '.log'); 177 | env['agent_args']['log_maxsize'] = constants.APPLOG_MAX_SIZE; 178 | env['agent_args']['log_maxfiles'] = constants.APPLOG_MAX_FILES; 179 | env['agent_args']['log_level'] = constants.APPLOG_LEVEL; 180 | } 181 | 182 | /* 183 | * Private env - HTTP Monitor Settings 184 | */ 185 | 186 | env['agent_args']['http_threshold'] = constants.TARS_MONITOR_HTTP_THRESHOLD; 187 | env['agent_args']['http_seppath'] = constants.TARS_MONITOR_HTTP_SEPPATH; 188 | env['agent_args']['http_socketerr'] = constants.TARS_MONITOR_HTTP_SOCKETERR; 189 | 190 | /* 191 | * Private env - Long Stack 192 | */ 193 | env['agent_args']['long_stack'] = constants.LONG_STACK; 194 | env['agent_args']['stack_usercode'] = constants.LONG_STACK_FILTER_USERCODE; 195 | 196 | /* 197 | * Private env - Reserved 198 | */ 199 | 200 | if (args['node_args']) { 201 | env['agent_args']['node_args'] = args['node_args'].join(','); 202 | } 203 | 204 | if (args['name']) { 205 | env['agent_args']['exec_name'] = args['name']; 206 | } 207 | 208 | // Object(agent_args) => JSON(agent_args) 209 | 210 | env['agent_args'] = JSON.stringify(env['agent_args']); 211 | 212 | /* 213 | * Public env 214 | */ 215 | 216 | if (args['http_ip']) { 217 | env['HTTP_IP'] = args['http_ip']; 218 | env['IP'] = args['http_ip']; 219 | } 220 | 221 | if (args['http_port']) { 222 | env['HTTP_PORT'] = args['http_port']; 223 | env['PORT'] = args['http_port']; 224 | } 225 | 226 | if (args['config']) { 227 | env['TARS_CONFIG'] = args['config']; 228 | } 229 | 230 | if (typeof args['tarsMonitor'] === 'boolean') { 231 | env['TARS_MONITOR'] = args['tarsMonitor']; 232 | } 233 | }; 234 | 235 | var setMonitor = function() { 236 | heartbeat_timer = setInterval(function() { 237 | var uptime = process.uptime(); 238 | allWorkers().forEach(function(worker) { 239 | if (worker._status === constants.WORKER_STATUS.ONLINE && uptime - worker._heartbeat > constants.WORKER_DETECT_INTERVAL) { 240 | events.emit('message', constants.GOD_MESSAGE.STOP_ZOMBIE_WORKER, worker); 241 | stopWorker(worker, true); 242 | } 243 | }); 244 | }, constants.WORKER_DETECT_INTERVAL * 1000); 245 | }; 246 | 247 | var prepare = function(args) { 248 | setArgs(args); 249 | setCluster(args['script_args'], args['node_args']); 250 | if (constants.WORKER_DETECT_INTERVAL > 0) { 251 | setMonitor(); 252 | } 253 | }; 254 | 255 | var startWorker = function(num) { 256 | var i = 0, seq = 0; 257 | 258 | num = num || 1; 259 | 260 | for (; i < num; i += 1) { 261 | seq = workers_seq.indexOf(false); 262 | if (seq === -1) { 263 | seq = workers_seq.length; 264 | workers_seq.push(true); 265 | } else { 266 | workers_seq[seq] = true; 267 | } 268 | 269 | env['WORKER_ID'] = seq; 270 | 271 | (function(worker) { 272 | worker._status = constants.WORKER_STATUS.LAUNCHING; 273 | worker._heartbeat = process.uptime(); 274 | worker._seq = seq; 275 | worker.once('error', function() { 276 | worker._status = constants.WORKER_STATUS.ERRORED; 277 | }).on('message', function(mesg) { 278 | cluster.emit('worker_message', worker, mesg); 279 | }); 280 | }(cluster.fork(env))); 281 | 282 | delete env['WORKER_ID']; 283 | } 284 | }; 285 | 286 | var killWorker = function(worker) { 287 | treekill(worker.process.pid, 'SIGTERM', function(err) { 288 | if (err) { 289 | events.emit('message', constants.GOD_MESSAGE.KILL_ERROR, worker, err); 290 | } 291 | }); 292 | }; 293 | 294 | var stopWorker = function(worker, err) { 295 | if (worker._status === constants.WORKER_STATUS.LAUNCHING || worker._status === constants.WORKER_STATUS.ONLINE) { 296 | events.emit('message', constants.GOD_MESSAGE.KILLING_WORKER, worker); 297 | 298 | worker._status = constants.WORKER_STATUS.STOPPING; 299 | if (err) { 300 | worker._hasError = true; 301 | } 302 | 303 | if (constants.GRACEFUL_TIMEOUT === 0) { 304 | killWorker(worker); 305 | return; 306 | } 307 | 308 | worker._timerId = setTimeout(function() { 309 | events.emit('message', constants.GOD_MESSAGE.FORCE_KILL_WORKER, worker); 310 | delete worker._timerId; 311 | 312 | killWorker(worker); 313 | }, constants.GRACEFUL_TIMEOUT); 314 | 315 | try { 316 | worker.send({ 317 | cmd : 'agent.shutdown' 318 | }); 319 | worker.disconnect(); 320 | } catch(e) { 321 | if (worker._timerId) { 322 | clearTimeout(worker._timerId); 323 | delete worker._timerId; 324 | } 325 | 326 | killWorker(worker); 327 | } 328 | } 329 | }; 330 | 331 | var send = function(message) { 332 | allWorkers().forEach(function(worker) { 333 | try { 334 | worker.send(message); 335 | } catch(e) {} 336 | }); 337 | }; 338 | 339 | var killAll = function() { 340 | events.emit('message', constants.GOD_MESSAGE.KILLING_ALL_WORKERS); 341 | allWorkers().forEach(function(worker) { 342 | stopWorker(worker); 343 | }); 344 | }; 345 | 346 | var getStatus = function(worker) { 347 | if (worker) { 348 | return worker._status; 349 | } 350 | 351 | return allWorkers().map(function(worker) { 352 | return worker._status; 353 | }); 354 | }; 355 | 356 | var canStartWorker = function() { 357 | if (exception_count === 0) { 358 | exception_timer = setTimeout(function() { 359 | exception_count = 0; 360 | exception_timer = undefined; 361 | }, constants.EXCEPTION_TIME); 362 | } 363 | 364 | exception_count += 1; 365 | 366 | if (exception_count >= constants.EXCEPTION_TOTAL) { 367 | if (exception_timer) { 368 | clearTimeout(exception_timer); 369 | exception_timer = undefined; 370 | } 371 | 372 | if (exception_count === constants.EXCEPTION_TOTAL) { 373 | events.emit('message', constants.GOD_MESSAGE.EXCEPTION_REACHED_COND); 374 | return constants.CAN_START_WORKER.NEED_TO_KILLALL; 375 | } 376 | 377 | return constants.CAN_START_WORKER.ALREADY_SEND_CMD; 378 | } 379 | return constants.CAN_START_WORKER.OK; 380 | }; 381 | 382 | var destroy = function() { 383 | if (heartbeat_timer) { 384 | clearInterval(heartbeat_timer); 385 | heartbeat_timer = undefined; 386 | } 387 | 388 | events.emit('message', constants.GOD_MESSAGE.ALL_WORKERS_STOPPED); 389 | }; 390 | 391 | module.exports = { 392 | prepare : prepare, 393 | startWorker : startWorker, 394 | stopWorker : stopWorker, 395 | killAll : killAll, 396 | getStatus : getStatus, 397 | events : events, 398 | send : send 399 | }; -------------------------------------------------------------------------------- /lib/ProcessContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var path = require('path'), 20 | util = require('util'); 21 | 22 | var winston = require('winston'), 23 | winstonTars = require('@tars/winston-tars'); 24 | 25 | var constants = require('./constants'); 26 | 27 | var callsite = require('callsite'); 28 | 29 | var httpStat = require('./tars/HttpStats'); 30 | var usageStat = require('./tars/UsageStats'); 31 | 32 | var agent_args = JSON.parse(process.env.agent_args); 33 | delete process.env.agent_args; 34 | 35 | var exec_script = agent_args.exec_script; 36 | 37 | var logger, currLogLevel; 38 | var longstack = null; 39 | 40 | var lineno = function() { 41 | var stack = callsite()[2]; 42 | return path.basename(stack.getFileName() || '') + ':' + stack.getLineNumber(); 43 | }; 44 | 45 | var errorToString = function(err) { 46 | if (typeof err === 'undefined') { 47 | return 'undefined'; 48 | } 49 | 50 | if (typeof err !== 'object') { 51 | return err.toString(); 52 | } 53 | 54 | if (!err) { 55 | return 'null'; 56 | } 57 | 58 | return err.stack ? err.stack : err.toString(); 59 | }; 60 | 61 | // set constants 62 | if (parseInt(agent_args.process_keepalive) >= 0) { 63 | constants.WORKER_DETECT_INTERVAL = parseInt(agent_args.process_keepalive); 64 | } 65 | 66 | // [en|dis]able tars monitor 67 | if (!process.env.TARS_CONFIG) { 68 | constants.TARS_MONITOR = false; 69 | } else if (process.env.TARS_MONITOR) { 70 | constants.TARS_MONITOR = (process.env.TARS_MONITOR === 'true'); 71 | } 72 | process.env.TARS_MONITOR = constants.TARS_MONITOR; 73 | 74 | // set process title 75 | if (exec_script) { 76 | process.title = util.format('%s: worker process', exec_script); 77 | } 78 | 79 | // fixed process script file path 80 | process.argv[1] = exec_script; 81 | 82 | // if we've been told to run as a different user or group (e.g. because they have fewer 83 | // privileges), switch to that user before importing any third party application code. 84 | if (agent_args.process_group) { 85 | process.setgid(agent_args.process_group); 86 | } 87 | 88 | if (agent_args.process_user) { 89 | process.setuid(agent_args.process_user); 90 | } 91 | 92 | // Handle Ctrl+C signal 93 | process.on('SIGINT', function() {}); 94 | 95 | // if script not listen on disconnect event, program will be exit 96 | process.on('disconnect', function disconnect() { 97 | if (constants.TARS_MONITOR) { 98 | httpStat.unbind(); 99 | usageStat.stop(); 100 | } 101 | 102 | if (longstack !== null) { 103 | longstack.disable(); 104 | longstack = null; 105 | } 106 | 107 | if (!process.listeners('disconnect').filter(function(listener) { 108 | return listener !== disconnect; 109 | }).length) { 110 | process.removeListener('disconnect', disconnect); 111 | process.exit(); 112 | } 113 | }); 114 | 115 | // Notify master that an uncaughtException has been catched 116 | process.on('uncaughtException', function uncaughtListener(err) { 117 | if (!process.listeners('uncaughtException').filter(function (listener) { 118 | return listener !== uncaughtListener; 119 | }).length) { 120 | process.removeListener('uncaughtListener', uncaughtListener); 121 | try { 122 | process.send({ 123 | cmd : 'god:err', 124 | data : errorToString(err) 125 | }); 126 | } catch(e) {} 127 | setTimeout(function() { 128 | process.exit(constants.CODE_UNCAUGHTEXCEPTION); 129 | }, 100); 130 | } 131 | }); 132 | 133 | // Main log settings 134 | if (!isNaN(parseInt(agent_args.log_maxsize))) { 135 | constants.APPLOG_MAX_SIZE = parseInt(agent_args.log_maxsize); 136 | } 137 | if (!isNaN(parseInt(agent_args.log_maxfiles))) { 138 | constants.APPLOG_MAX_FILES = parseInt(agent_args.log_maxfiles); 139 | } 140 | if (agent_args.log_level) { 141 | constants.APPLOG_LEVEL = agent_args.log_level; 142 | } 143 | 144 | // init logger 145 | if (agent_args.log_main) { 146 | logger = new (winston.Logger)({ 147 | transports: [new (winston.transports.TarsRotate)({ 148 | filename : agent_args.log_main, 149 | maxSize : constants.APPLOG_MAX_SIZE, 150 | maxFiles : constants.APPLOG_MAX_FILES 151 | })] 152 | }); 153 | } else { 154 | logger = new (winston.Logger)({ 155 | transports : [new (winston.transports.Console)({ 156 | formatter : winstonTars.Formatter.Detail() 157 | })] 158 | }); 159 | } 160 | 161 | logger.setLevels(winston.config.tars.levels); 162 | logger.emitErrs = true; 163 | 164 | // Redirect console to master 165 | console.log = function() { 166 | if (currLogLevel >= winston.config.tars.levels.debug) { 167 | var argsLen = arguments.length; 168 | var args = new Array(argsLen); 169 | for (var i = 0; i < argsLen; i += 1) { 170 | args[i] = arguments[i]; 171 | } 172 | 173 | logger.log('debug', util.format.apply(util, args), { 174 | lineno : lineno() 175 | }); 176 | } 177 | }; 178 | console.info = function() { 179 | if (currLogLevel >= winston.config.tars.levels.info) { 180 | var argsLen = arguments.length; 181 | var args = new Array(argsLen); 182 | for (var i = 0; i < argsLen; i += 1) { 183 | args[i] = arguments[i]; 184 | } 185 | 186 | logger.log('info', util.format.apply(util, args), { 187 | lineno : lineno() 188 | }); 189 | } 190 | }; 191 | console.warn = function() { 192 | if (currLogLevel >= winston.config.tars.levels.warn) { 193 | var argsLen = arguments.length; 194 | var args = new Array(argsLen); 195 | for (var i = 0; i < argsLen; i += 1) { 196 | args[i] = arguments[i]; 197 | } 198 | 199 | logger.log('warn', util.format.apply(util, args), { 200 | lineno : lineno() 201 | }); 202 | } 203 | }; 204 | console.error = function() { 205 | if (currLogLevel >= winston.config.tars.levels.error) { 206 | var argsLen = arguments.length; 207 | var args = new Array(argsLen); 208 | for (var i = 0; i < argsLen; i += 1) { 209 | args[i] = arguments[i]; 210 | } 211 | 212 | logger.log('error', util.format.apply(util, args), { 213 | lineno : lineno() 214 | }); 215 | } 216 | }; 217 | 218 | var setLevel = function(level) { 219 | if (typeof level !== 'string') { 220 | return; 221 | } 222 | level = level.toLowerCase(); 223 | if (Object.getOwnPropertyNames(winston.config.tars.levels).indexOf(level) === -1) { 224 | return; 225 | } 226 | Object.getOwnPropertyNames(logger.transports).forEach(function(name) { 227 | logger.transports[name].level = level; 228 | }); 229 | currLogLevel = winston.config.tars.levels[level]; 230 | }; 231 | 232 | setLevel(constants.APPLOG_LEVEL); 233 | 234 | // process log level change 235 | process.on('message', function(message) { 236 | if (message) { 237 | switch(message.cmd) { 238 | case 'tars.setloglevel' : { 239 | setLevel(message.data); 240 | break; 241 | } 242 | case 'agent.shutdown' : { 243 | if (process.connected) { 244 | process.disconnect(); 245 | } 246 | break; 247 | } 248 | } 249 | } 250 | }); 251 | 252 | // send heartbeat to master 253 | if (constants.WORKER_DETECT_INTERVAL > 0) { 254 | setInterval(function() { 255 | try { 256 | process.send({ 257 | cmd : 'god:alive' 258 | }, undefined, function() {}); 259 | } catch(e) {} 260 | }, Math.ceil(constants.WORKER_DETECT_INTERVAL * 1000 / constants.WORKER_HEART_BEAT_TIMES)).unref(); 261 | } 262 | 263 | // monitor http & https svr 264 | if (constants.TARS_MONITOR) { 265 | httpStat.bind({ 266 | threshold : agent_args.http_threshold, 267 | sep : agent_args.http_seppath, 268 | socketerr : agent_args.http_socketerr 269 | }); 270 | usageStat.start(); 271 | } 272 | 273 | // Change dir to fix process.cwd 274 | process.chdir(path.dirname(exec_script)); 275 | 276 | // Long Stack 277 | if (agent_args.long_stack) { 278 | longstack = require('longstack'); 279 | longstack.enable({ 280 | 'removeNativeCode' : agent_args.stack_usercode 281 | }); 282 | } 283 | 284 | // Get the script & exec as main 285 | require('module')._load(exec_script, null, true); -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var conf = { 20 | ENTRY_POINT_NAME : ['server.js', 'app.js', 'start.js', 'index.js'], 21 | GRACEFUL_TIMEOUT : 1000 * 8, 22 | EXCEPTION_TOTAL : 2, 23 | EXCEPTION_TIME : 1000 * 10, 24 | APPLOG_MAX_FILES : 10, 25 | APPLOG_MAX_SIZE : 1024 * 1024 * 10, 26 | APPLOG_LEVEL : 'DEBUG', 27 | WORKER_STATUS : { 28 | ONLINE : 1, 29 | STOPPED : 2, 30 | STOPPING : 3, 31 | LAUNCHING : 4, 32 | ERRORED : 5 33 | }, 34 | GOD_MESSAGE : { 35 | EXCEPTION_REACHED_COND : 1, 36 | KILLING_ALL_WORKERS : 2, 37 | KILLING_WORKER : 3, 38 | FORCE_KILL_WORKER : 4, 39 | ALL_WORKERS_STOPPED : 5, 40 | STOP_ZOMBIE_WORKER : 6, 41 | KILL_ERROR : 7 42 | }, 43 | CAN_START_WORKER : { 44 | OK : 1, 45 | NEED_TO_KILLALL : 2, 46 | ALREADY_SEND_CMD : 3 47 | }, 48 | TARS_DEFAULT_APP : 'DEFAULT_APP', 49 | TARS_HEART_BEAT_INTERVAL : 10 * 1000, 50 | TARS_MONITOR : true, 51 | TARS_MONITOR_HTTP_THRESHOLD : 400, 52 | TARS_MONITOR_HTTP_SEPPATH : true, 53 | TARS_MONITOR_HTTP_SOCKETERR : true, 54 | WORKER_HEART_BEAT_TIMES : 5, 55 | WORKER_DETECT_INTERVAL : 5 * 60, 56 | CODE_UNCAUGHTEXCEPTION : 100, 57 | LONG_STACK : false, 58 | LONG_STACK_FILTER_USERCODE : false 59 | }; 60 | 61 | module.exports = conf; -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var path = require('path'), 20 | cluster = require('cluster'); 21 | 22 | var winston = require('winston'), 23 | winstonTars = require('@tars/winston-tars'); 24 | 25 | var servername, dirname; 26 | 27 | var isLogToFile = function() { 28 | return !!(servername && dirname); 29 | }; 30 | 31 | var initLogger = function(name, type, options) { 32 | var transport, logger; 33 | 34 | if (!isLogToFile()) { 35 | logger = winston.loggers.add(name, {transports : [new winston.transports.Console({ 36 | formatter : winstonTars.Formatter.Detail() 37 | })]}); 38 | logger.emitErrs = true; 39 | logger.setLevels(winston.config.tars.levels); 40 | return true; 41 | } 42 | 43 | transport = winston.transports[type === 'TarsRotate' ? 'TarsBase' : type]; 44 | 45 | if (!transport) { 46 | return false; 47 | } 48 | 49 | if (name !== '_global') { 50 | options.filename = path.join(dirname, servername + '_' + name + '.log'); 51 | } else { 52 | options.filename = path.join(dirname, servername + '.log'); 53 | } 54 | 55 | transport = new transport(options); 56 | 57 | if (type === 'TarsRotate') { 58 | transport.on('checkfile', function() { 59 | winston.transports.TarsRotate.Master.start(this.filename, this.interval, options.maxFiles, options.maxSize, '_'); 60 | }); 61 | } 62 | 63 | logger = winston.loggers.add(name, {transports : [transport]}); 64 | logger.emitErrs = true; 65 | logger.setLevels(winston.config.tars.levels); 66 | 67 | return true; 68 | }; 69 | 70 | exports.prepare = function(name, dir) { 71 | dirname = dir; 72 | servername = name; 73 | 74 | winston.addColors(winston.config.tars.colors); 75 | 76 | cluster.on('worker_message', function(worker, message) { 77 | var data = message.data; 78 | if (message.cmd === 'log:rotate') { 79 | winston.transports.TarsRotate.Master.start(data.filename, data.interval, data.maxFiles, data.maxSize, data.concatStr); 80 | } 81 | }); 82 | }; 83 | 84 | exports.init = function(name, type, options) { 85 | name = name || '_global'; 86 | 87 | if (winston.loggers.has(name)) { 88 | return true; 89 | } 90 | 91 | return initLogger(name, type, options); 92 | }; 93 | 94 | exports.append = function(name, data) { 95 | name = name || '_global'; 96 | 97 | var logger = winston.loggers.get(name); 98 | if (logger && typeof logger[data.level] === 'function') { 99 | logger.log(data.level, data.msg, data.meta); 100 | } 101 | }; 102 | 103 | exports.setLevel = function(level, name) { 104 | var keys; 105 | 106 | if (typeof level !== 'string') { 107 | return false; 108 | } 109 | level = level.toLowerCase(); 110 | 111 | if (Object.getOwnPropertyNames(winston.config.tars.levels).indexOf(level) === -1) { 112 | return false; 113 | } 114 | 115 | switch(typeof name) { 116 | case 'object' : { 117 | keys = ['_global']; 118 | break; 119 | } 120 | case 'string' : { 121 | keys = [name]; 122 | break; 123 | } 124 | default : { 125 | keys = Object.getOwnPropertyNames(winston.loggers.loggers); 126 | break; 127 | } 128 | } 129 | 130 | keys.forEach(function(key) { 131 | var logger = winston.loggers.loggers[key]; 132 | Object.getOwnPropertyNames(logger.transports).forEach(function(name) { 133 | logger.transports[name].level = level; 134 | }); 135 | }); 136 | 137 | return true; 138 | }; 139 | 140 | exports.close = function() { 141 | winston.transports.TarsRotate.Master.close(); 142 | }; 143 | 144 | exports.isLogToFile = isLogToFile; -------------------------------------------------------------------------------- /lib/tars/HttpStats/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var http = require('http'), 20 | https = require('https'), 21 | assert = require('assert'); 22 | 23 | var onFinished = require('on-finished'); 24 | 25 | var tarsMonitor = require('@tars/monitor').stat, 26 | tarsConfigure = require('@tars/utils').Config; 27 | 28 | var moduleName; 29 | var slaveSetName, slaveSetArea, slaveSetID; 30 | var createServer = {}; 31 | 32 | var pathname = function(url) { 33 | var hash, qs, path; 34 | 35 | if (!url || typeof url !== 'string') { 36 | return '/'; 37 | } 38 | 39 | if (url.indexOf('http://') === 0) { 40 | path = url.indexOf('/', 7); 41 | } else if (url.indexOf('https://') === 0) { 42 | path = url.indexOf('/', 8); 43 | } 44 | 45 | if (path !== undefined) { 46 | if (path !== -1) { 47 | url = url.slice(path); 48 | } else { 49 | return '/'; 50 | } 51 | } 52 | 53 | hash = url.indexOf('#'); 54 | 55 | if (hash !== -1) { 56 | url = url.slice(0, hash); 57 | } 58 | 59 | qs = url.indexOf('?'); 60 | 61 | if (qs !== -1) { 62 | url = url.slice(0, qs); 63 | } 64 | 65 | return url || '/'; 66 | }; 67 | 68 | var initConfig = function(obj) { 69 | var tarsConfig, setdivision; 70 | 71 | obj = obj || process.env.TARS_CONFIG; 72 | 73 | assert(obj, 'TARS_CONFIG is not in env and init argument is neither an Object nor a String.'); 74 | 75 | if (typeof obj === 'string') { 76 | tarsConfig = new tarsConfigure; 77 | tarsConfig.parseFile(obj); 78 | } else { 79 | tarsConfig = obj; 80 | } 81 | 82 | moduleName = tarsConfig.get('tars.application.client.modulename') || 'NO_MODULE_NAME'; 83 | setdivision = tarsConfig.get('tars.application.setdivision'); 84 | 85 | if (tarsConfig.get('tars.application.enableset', '').toLowerCase() === 'y' && setdivision && typeof setdivision === 'string') { 86 | setdivision = setdivision.split('.'); 87 | if (setdivision.length >= 3) { 88 | slaveSetName = setdivision[0]; 89 | slaveSetArea = setdivision[1]; 90 | slaveSetID = setdivision.slice(2).join('.'); 91 | 92 | moduleName += '.' + slaveSetName + slaveSetArea + slaveSetID; 93 | } 94 | } 95 | }; 96 | 97 | var onFinished_callback = function(err, res) { 98 | var reqInfo = res.__stat_req_info; 99 | 100 | if ((reqInfo.socketerr && err) || (reqInfo.threshold !== 0 && res.statusCode >= reqInfo.threshold)) { 101 | tarsMonitor.report(reqInfo.report, tarsMonitor.TYPE.ERROR); 102 | } else { 103 | tarsMonitor.report(reqInfo.report, tarsMonitor.TYPE.SUCCESS, (process.uptime() - reqInfo.startTime) * 1000); 104 | } 105 | }; 106 | 107 | var shimming = function(type, options) { 108 | switch (type) { 109 | case 'http' : { 110 | createServer[type] = createServer[type] || http.createServer; 111 | break; 112 | } 113 | case 'https' : { 114 | createServer[type] = createServer[type] || https.createServer; 115 | break; 116 | } 117 | } 118 | 119 | return function(/*...args*/) { 120 | var reqHandler = function(req, res) { 121 | var localInfo = req.socket.address(); 122 | 123 | var reqInfo = { 124 | 'report' : { 125 | 'masterName' : type + '_client', 126 | 'slaveName' : moduleName, 127 | 'interfaceName' : '', 128 | 'masterIp' : req.socket.remoteAddress || '', 129 | 'slaveIp' : localInfo.address || '', 130 | 'slavePort' : localInfo.port || 0, 131 | 'bFromClient' : false 132 | }, 133 | 'startTime' : process.uptime(), 134 | 'threshold' : options.threshold, 135 | 'socketerr' : options.socketerr 136 | }; 137 | 138 | if (options.sep) { 139 | reqInfo.report.interfaceName = pathname(req.url); 140 | } 141 | 142 | if (slaveSetName && slaveSetArea && slaveSetID) { 143 | reqInfo.report.slaveSetName = slaveSetName; 144 | reqInfo.report.slaveSetArea = slaveSetArea; 145 | reqInfo.report.slaveSetID = slaveSetID; 146 | } 147 | 148 | res.__stat_req_info = reqInfo; 149 | 150 | onFinished(res, onFinished_callback); 151 | 152 | /* 153 | * don't leak arguments to the other function 154 | */ 155 | var argsLen = arguments.length, 156 | args = new Array(argsLen); 157 | 158 | for (var i = 0; i < argsLen; i += 1) { 159 | args[i] = arguments[i]; 160 | } 161 | 162 | requestListener && requestListener.apply(this, args); 163 | }, requestListener; 164 | 165 | switch (type) { 166 | case 'http' : { 167 | var serverOptions = arguments[0]; 168 | requestListener = arguments[1]; 169 | if(typeof arguments[0] === 'function'){ 170 | requestListener = arguments[0]; 171 | serverOptions = {}; 172 | } 173 | return createServer[type].call(http, serverOptions, reqHandler); 174 | } 175 | case 'https' : { 176 | requestListener = arguments[1]; 177 | return createServer[type].call(https, arguments[0], reqHandler); 178 | } 179 | } 180 | }; 181 | }; 182 | 183 | var restore = function(type) { 184 | if (!createServer[type]) { 185 | return; 186 | } 187 | 188 | switch (type) { 189 | case 'http' : { 190 | http.createServer = createServer[type]; 191 | break; 192 | } 193 | case 'https' : { 194 | https.createServer = createServer[type]; 195 | break; 196 | } 197 | } 198 | 199 | delete createServer[type]; 200 | }; 201 | 202 | exports.init = function(obj) { 203 | initConfig(obj); 204 | tarsMonitor.init(obj); 205 | }; 206 | exports.bind = function(options) { 207 | var opt = { 208 | 'threshold' : 400, 209 | 'sep' : true, 210 | 'socketerr' : true 211 | }; 212 | 213 | if (options) { 214 | if (options.threshold >= 0) { 215 | opt.threshold = options.threshold; 216 | } 217 | if (typeof options.sep === 'boolean') { 218 | opt.sep = options.sep; 219 | } 220 | if (typeof options.socketerr === 'boolean') { 221 | opt.socketerr = options.socketerr; 222 | } 223 | } 224 | 225 | if (!moduleName) { 226 | exports.init(); 227 | } 228 | 229 | http.createServer = shimming('http', opt); 230 | https.createServer = shimming('https', opt); 231 | }; 232 | exports.unbind = function() { 233 | if (moduleName) { 234 | restore('http'); 235 | restore('https'); 236 | 237 | tarsMonitor.stop(); 238 | } 239 | }; -------------------------------------------------------------------------------- /lib/tars/Message/AdminF.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | "use strict"; 18 | 19 | var assert = require("assert"); 20 | var TarsStream = require("@tars/stream"); 21 | var TarsError = require("@tars/rpc").error; 22 | 23 | var tars = tars || {}; 24 | module.exports.tars = tars; 25 | 26 | tars.AdminFImp = function () { 27 | this._name = undefined; 28 | this._worker = undefined; 29 | }; 30 | 31 | tars.AdminFImp.prototype.initialize = function () {}; 32 | 33 | tars.AdminFImp.prototype.onDispatch = function (current, funcName, binBuffer) { 34 | if ("__" + funcName in this) { 35 | return this["__" + funcName](current, binBuffer); 36 | } else { 37 | return TarsError.SERVER.FUNC_NOT_FOUND; 38 | } 39 | }; 40 | 41 | var __tars_AdminF$tars_ping$RE = function (_ret) { 42 | if (this.getRequestVersion() === TarsStream.Tup.TUP_SIMPLE || this.getRequestVersion() === TarsStream.Tup.TUP_COMPLEX) { 43 | var tup = new TarsStream.UniAttribute(); 44 | tup.tupVersion = this.getRequestVersion(); 45 | tup.writeInt32("", _ret); 46 | 47 | this.doResponse(tup.encode()); 48 | } else { 49 | var os = new TarsStream.TarsOutputStream(); 50 | os.writeInt32(0, _ret); 51 | 52 | this.doResponse(os.getBinBuffer()); 53 | } 54 | }; 55 | 56 | tars.AdminFImp.prototype.__tars_ping = function (current) { 57 | __tars_AdminF$tars_ping$RE.call(current, 0); 58 | 59 | return TarsError.SUCCESS; 60 | }; 61 | 62 | tars.AdminFImp.prototype.shutdown = function () { 63 | assert.fail("shutdown function not implemented"); 64 | }; 65 | 66 | var __tars_AdminF$shutdown$RE = function () { 67 | this.doResponse(new TarsStream.BinBuffer()); 68 | }; 69 | 70 | tars.AdminFImp.prototype.__shutdown = function (current) { 71 | current.sendResponse = __tars_AdminF$shutdown$RE; 72 | 73 | this.shutdown(current); 74 | 75 | return TarsError.SUCCESS; 76 | }; 77 | 78 | tars.AdminFImp.prototype.notify = function () { 79 | assert.fail("notify function not implemented"); 80 | }; 81 | 82 | var __tars_AdminF$notify$RE = function (_ret) { 83 | if (this.getRequestVersion() === TarsStream.Tup.TUP_SIMPLE || this.getRequestVersion() === TarsStream.Tup.TUP_COMPLEX) { 84 | var tup = new TarsStream.UniAttribute(); 85 | tup.tupVersion = this.getRequestVersion(); 86 | tup.writeString("", _ret); 87 | 88 | this.doResponse(tup.encode()); 89 | } else { 90 | var os = new TarsStream.TarsOutputStream(); 91 | os.writeString(0, _ret); 92 | 93 | this.doResponse(os.getBinBuffer()); 94 | } 95 | }; 96 | 97 | tars.AdminFImp.prototype.__notify = function (current, binBuffer) { 98 | var command = null; 99 | 100 | if (current.getRequestVersion() === TarsStream.Tup.TUP_SIMPLE || current.getRequestVersion() === TarsStream.Tup.TUP_COMPLEX) { 101 | var tup = new TarsStream.UniAttribute(); 102 | tup.tupVersion = current.getRequestVersion(); 103 | tup.decode(binBuffer); 104 | command = tup.readString("command"); 105 | } else { 106 | var is = new TarsStream.TarsInputStream(binBuffer); 107 | command = is.readString(1, true, ""); 108 | } 109 | 110 | current.sendResponse = __tars_AdminF$notify$RE; 111 | 112 | this.notify(current, command); 113 | 114 | return TarsError.SUCCESS; 115 | }; 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /lib/tars/Message/AdminF.tars: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | module tars 18 | { 19 | interface AdminF 20 | { 21 | /** 22 | * 关闭服务 23 | */ 24 | void shutdown(); 25 | 26 | /** 27 | * 通知服务 28 | */ 29 | string notify(string command); 30 | }; 31 | }; -------------------------------------------------------------------------------- /lib/tars/Message/AdminFImp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var tars = require('./AdminF.js').tars; 20 | module.exports.tars = tars; 21 | 22 | tars.AdminFImp.prototype.initialize = function () {} 23 | 24 | tars.AdminFImp.prototype.shutdown = function (current) { 25 | this._mesgEvents.emit('shutdown'); 26 | current.sendResponse(); 27 | } 28 | 29 | tars.AdminFImp.prototype.notify = function (current, command) { 30 | var cmd = command.split(' ')[0], 31 | data = command.slice(cmd.length + 1), 32 | msg = 'ok'; 33 | 34 | this._mesgEvents.emit('notify', cmd, data, function(ret) { 35 | msg = ret; 36 | }); 37 | 38 | current.sendResponse(msg); 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/tars/Message/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var util = require("util"); 20 | var events = require("events"); 21 | 22 | var TarsServer = require('@tars/rpc').server; 23 | var AdminF = require('./AdminFImp'); 24 | 25 | function tarsMessage() { 26 | events.EventEmitter.call(this); 27 | } 28 | 29 | util.inherits(tarsMessage, events.EventEmitter); 30 | 31 | tarsMessage.prototype.startServer = function(name, localString) { 32 | var servantName = util.format('%s.%s', name, 'AdminObj'), ths = this; 33 | 34 | if (this._server) { 35 | return; 36 | } 37 | 38 | AdminF.tars.AdminFImp.prototype._mesgEvents = ths; 39 | 40 | this._server = TarsServer.createServer(AdminF.tars.AdminFImp); 41 | this._server.start({ 42 | name : servantName + 'Adapter', 43 | servant : servantName, 44 | endpoint : localString, 45 | maxconns : 200000, 46 | protocol : 'tars' 47 | }); 48 | }; 49 | 50 | tarsMessage.prototype.destroy = function() { 51 | if (this._server) { 52 | this._server.stop(); 53 | this._server = undefined; 54 | } 55 | delete AdminF.tars.AdminFImp.prototype._mesgEvents; 56 | }; 57 | 58 | module.exports = exports = new tarsMessage(); -------------------------------------------------------------------------------- /lib/tars/Notify/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var tarsNotify = require('@tars/notify'); 20 | 21 | var initialized = false; 22 | 23 | var init = function(config) { 24 | if (config) { 25 | tarsNotify.init(config); 26 | initialized = true; 27 | } 28 | }; 29 | 30 | var report = function(level, message, id) { 31 | if (initialized) { 32 | if (level === tarsNotify.LEVEL.NOTIFYERROR) { 33 | message = '[alarm] ' + message; 34 | } 35 | tarsNotify.report(message, id); 36 | } 37 | }; 38 | 39 | exports.init = init; 40 | exports.report = { 41 | error : function(message, id) { 42 | report(tarsNotify.LEVEL.NOTIFYERROR, message, id); 43 | }, 44 | info : function(message, id) { 45 | report(tarsNotify.LEVEL.NOTIFYNORMAL, message, id); 46 | } 47 | }; -------------------------------------------------------------------------------- /lib/tars/Report/NodeF.tars: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | module tars 18 | { 19 | struct ServerInfo 20 | { 21 | 0 require string application; 22 | 1 require string serverName; 23 | 2 require int pid; 24 | 3 optional string adapter; 25 | }; 26 | 27 | interface ServerF 28 | { 29 | /** 30 | * 向node定时上报serverInfo 31 | * @param serverInfo 服务状态 32 | * @return int 33 | */ 34 | int keepAlive(ServerInfo serverInfo); 35 | 36 | /** 37 | * 向node上报TARS版本信息 38 | * @param string 版本信息 39 | * @return int 40 | */ 41 | int reportVersion(string app,string serverName,string version); 42 | }; 43 | }; -------------------------------------------------------------------------------- /lib/tars/Report/NodeFProxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | "use strict"; 18 | 19 | var assert = require("assert"); 20 | var TarsStream = require("@tars/stream"); 21 | var TarsError = require("@tars/rpc").error; 22 | 23 | var tars = tars || {}; 24 | module.exports.tars = tars; 25 | 26 | tars.ServerFProxy = function () { 27 | this._name = undefined; 28 | this._worker = undefined; 29 | }; 30 | 31 | tars.ServerFProxy.prototype.setTimeout = function (iTimeout) { 32 | this._worker.timeout = iTimeout; 33 | }; 34 | 35 | tars.ServerFProxy.prototype.getTimeout = function () { 36 | return this._worker.timeout; 37 | }; 38 | 39 | 40 | tars.ServerInfo = function() { 41 | this.application = ""; 42 | this.serverName = ""; 43 | this.pid = 0; 44 | this.adapter = ""; 45 | this._classname = "tars.ServerInfo"; 46 | }; 47 | tars.ServerInfo._classname = "tars.ServerInfo"; 48 | tars.ServerInfo._write = function (os, tag, value) { os.writeStruct(tag, value); }; 49 | tars.ServerInfo._read = function (is, tag, def) { return is.readStruct(tag, true, def); }; 50 | tars.ServerInfo._readFrom = function (is) { 51 | var tmp = new tars.ServerInfo(); 52 | tmp.application = is.readString(0, true, ""); 53 | tmp.serverName = is.readString(1, true, ""); 54 | tmp.pid = is.readInt32(2, true, 0); 55 | tmp.adapter = is.readString(3, false, ""); 56 | return tmp; 57 | }; 58 | tars.ServerInfo.prototype._writeTo = function (os) { 59 | os.writeString(0, this.application); 60 | os.writeString(1, this.serverName); 61 | os.writeInt32(2, this.pid); 62 | os.writeString(3, this.adapter); 63 | }; 64 | tars.ServerInfo.prototype._equal = function () { 65 | assert.fail("this structure not define key operation"); 66 | }; 67 | tars.ServerInfo.prototype._genKey = function () { 68 | if (!this._proto_struct_name_) { 69 | this._proto_struct_name_ = "STRUCT" + Math.random(); 70 | } 71 | return this._proto_struct_name_; 72 | }; 73 | tars.ServerInfo.prototype.toObject = function() { 74 | return { 75 | "application" : this.application, 76 | "serverName" : this.serverName, 77 | "pid" : this.pid, 78 | "adapter" : this.adapter 79 | }; 80 | }; 81 | tars.ServerInfo.prototype.readFromObject = function(json) { 82 | json.hasOwnProperty("application") && (this.application = json.application); 83 | json.hasOwnProperty("serverName") && (this.serverName = json.serverName); 84 | json.hasOwnProperty("pid") && (this.pid = json.pid); 85 | json.hasOwnProperty("adapter") && (this.adapter = json.adapter); 86 | }; 87 | tars.ServerInfo.prototype.toBinBuffer = function () { 88 | var os = new TarsStream.TarsOutputStream(); 89 | this._writeTo(os); 90 | return os.getBinBuffer(); 91 | }; 92 | tars.ServerInfo.new = function () { 93 | return new tars.ServerInfo(); 94 | }; 95 | tars.ServerInfo.create = function (is) { 96 | return tars.ServerInfo._readFrom(is); 97 | }; 98 | 99 | 100 | var __tars_ServerF$keepAlive$EN = function (serverInfo) { 101 | var os = new TarsStream.TarsOutputStream(); 102 | os.writeStruct(1, serverInfo); 103 | return os.getBinBuffer(); 104 | }; 105 | 106 | var __tars_ServerF$keepAlive$DE = function (data) { 107 | try { 108 | var is = new TarsStream.TarsInputStream(data.response.sBuffer); 109 | return { 110 | "request" : data.request, 111 | "response" : { 112 | "costtime" : data.request.costtime, 113 | "return" : is.readInt32(0, true, 0) 114 | } 115 | }; 116 | } catch (e) { 117 | throw { 118 | "request" : data.request, 119 | "response" : { 120 | "costtime" : data.request.costtime, 121 | "error" : { 122 | "code" : TarsError.CLIENT.DECODE_ERROR, 123 | "message" : e.message 124 | } 125 | } 126 | }; 127 | } 128 | }; 129 | 130 | var __tars_ServerF$keepAlive$ER = function (data) { 131 | throw { 132 | "request" : data.request, 133 | "response" : { 134 | "costtime" : data.request.costtime, 135 | "error" : data.error 136 | } 137 | } 138 | }; 139 | 140 | tars.ServerFProxy.prototype.keepAlive = function (serverInfo) { 141 | return this._worker.tars_invoke("keepAlive", __tars_ServerF$keepAlive$EN(serverInfo), arguments[arguments.length - 1]).then(__tars_ServerF$keepAlive$DE, __tars_ServerF$keepAlive$ER); 142 | }; 143 | 144 | var __tars_ServerF$reportVersion$EN = function (app, serverName, version) { 145 | var os = new TarsStream.TarsOutputStream(); 146 | os.writeString(1, app); 147 | os.writeString(2, serverName); 148 | os.writeString(3, version); 149 | return os.getBinBuffer(); 150 | }; 151 | 152 | var __tars_ServerF$reportVersion$DE = function (data) { 153 | try { 154 | var is = new TarsStream.TarsInputStream(data.response.sBuffer); 155 | return { 156 | "request" : data.request, 157 | "response" : { 158 | "costtime" : data.request.costtime, 159 | "return" : is.readInt32(0, true, 0) 160 | } 161 | }; 162 | } catch (e) { 163 | throw { 164 | "request" : data.request, 165 | "response" : { 166 | "costtime" : data.request.costtime, 167 | "error" : { 168 | "code" : TarsError.CLIENT.DECODE_ERROR, 169 | "message" : e.message 170 | } 171 | } 172 | }; 173 | } 174 | }; 175 | 176 | var __tars_ServerF$reportVersion$ER = function (data) { 177 | throw { 178 | "request" : data.request, 179 | "response" : { 180 | "costtime" : data.request.costtime, 181 | "error" : data.error 182 | } 183 | } 184 | }; 185 | 186 | tars.ServerFProxy.prototype.reportVersion = function (app, serverName, version) { 187 | return this._worker.tars_invoke("reportVersion", __tars_ServerF$reportVersion$EN(app, serverName, version), arguments[arguments.length - 1]).then(__tars_ServerF$reportVersion$DE, __tars_ServerF$reportVersion$ER); 188 | }; 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /lib/tars/Report/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var TarsClient = require('@tars/rpc').Communicator.New(); 20 | var NodeF = require('./NodeFProxy'); 21 | 22 | var constants = require('../../constants'); 23 | 24 | var serverInfo; 25 | var timer_id, client, emptyfn = function() {}; 26 | 27 | exports.init = function(name, servant) { 28 | var info = name.split('.'); 29 | 30 | serverInfo = new NodeF.tars.ServerInfo(); 31 | 32 | if (info.length > 1) { 33 | serverInfo.application = info[0]; 34 | serverInfo.serverName = info[1]; 35 | } else { 36 | serverInfo.application = constants.TARS_DEFAULT_APP; 37 | serverInfo.serverName = info[0]; 38 | } 39 | serverInfo.pid = process.pid; 40 | 41 | client = TarsClient.stringToProxy(NodeF.tars.ServerFProxy, servant); 42 | }; 43 | 44 | exports.keepAlive = function(interval) { 45 | if (!timer_id) { 46 | timer_id = setInterval(function() { 47 | client && client.keepAlive(serverInfo).catch(emptyfn); 48 | }, interval || constants.TARS_HEART_BEAT_INTERVAL); 49 | client.keepAlive(serverInfo).catch(emptyfn); 50 | } 51 | }; 52 | 53 | exports.destroy = function() { 54 | if (timer_id) { 55 | clearInterval(timer_id); 56 | timer_id = undefined; 57 | } 58 | 59 | if (client) { 60 | TarsClient.disconnect(); 61 | client = undefined; 62 | } 63 | 64 | serverInfo = undefined; 65 | }; 66 | 67 | exports.reportVersion = function(version) { 68 | client && client.reportVersion(serverInfo.application, serverInfo.serverName, version).catch(emptyfn); 69 | }; -------------------------------------------------------------------------------- /lib/tars/UsageStats/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var lag = require('event-loop-lag'); 6 | var pidusage = require('pidusage'); 7 | 8 | var tarsMonitor = require('@tars/monitor').property; 9 | var tarsConfigure = require('@tars/utils').Config; 10 | 11 | var eventloop; 12 | 13 | var moduleName; 14 | 15 | var memTimerId, lagTimerId, cpuTimerId, libuvTimerId; 16 | 17 | var rss, heapTotal, heapUsed, eventloopLag, cpuUsage, activeHandles, activeRequests; 18 | 19 | var initConfig = function(obj) { 20 | var tarsConfig, setdivision, slaveSetName, slaveSetArea, slaveSetID; 21 | 22 | obj = obj || process.env.TARS_CONFIG; 23 | 24 | assert(obj, 'TARS_CONFIG is not in env and init argument is neither an Object nor a String.'); 25 | 26 | tarsMonitor.init(obj); 27 | 28 | if (typeof obj === 'string') { 29 | tarsConfig = new tarsConfigure; 30 | tarsConfig.parseFile(obj); 31 | } else { 32 | tarsConfig = obj; 33 | } 34 | 35 | moduleName = tarsConfig.get('tars.application.client.modulename', ''); 36 | setdivision = tarsConfig.get('tars.application.setdivision'); 37 | 38 | if (tarsConfig.get('tars.application.enableset', '').toLowerCase() === 'y' && setdivision && typeof setdivision === 'string') { 39 | setdivision = setdivision.split('.'); 40 | if (setdivision.length >= 3) { 41 | slaveSetName = setdivision[0]; 42 | slaveSetArea = setdivision[1]; 43 | slaveSetID = setdivision.slice(2).join('.'); 44 | if (moduleName.indexOf('.') !== -1) { 45 | moduleName = moduleName.slice(moduleName.indexOf('.') + 1); 46 | } 47 | moduleName = slaveSetName + slaveSetArea + slaveSetID + '.' + moduleName; 48 | } 49 | } 50 | }; 51 | 52 | var initReporter = function() { 53 | rss = tarsMonitor.create(moduleName + '.rss', [new tarsMonitor.POLICY.Max, 54 | new tarsMonitor.POLICY.Min, 55 | new tarsMonitor.POLICY.Avg]); 56 | 57 | heapTotal = tarsMonitor.create(moduleName + '.heapTotal', [new tarsMonitor.POLICY.Max, 58 | new tarsMonitor.POLICY.Min, 59 | new tarsMonitor.POLICY.Avg]); 60 | 61 | heapUsed = tarsMonitor.create(moduleName + '.heapUsed', [new tarsMonitor.POLICY.Max, 62 | new tarsMonitor.POLICY.Min, 63 | new tarsMonitor.POLICY.Avg]); 64 | 65 | eventloopLag = tarsMonitor.create(moduleName + '.eventLoop', [new tarsMonitor.POLICY.Max, 66 | new tarsMonitor.POLICY.Min, 67 | new tarsMonitor.POLICY.Avg]); 68 | 69 | cpuUsage = tarsMonitor.create(moduleName + '.cpuUsage', [new tarsMonitor.POLICY.Max, 70 | new tarsMonitor.POLICY.Min, 71 | new tarsMonitor.POLICY.Avg]); 72 | 73 | activeHandles = tarsMonitor.create(moduleName + '.activeHandles', [new tarsMonitor.POLICY.Max, 74 | new tarsMonitor.POLICY.Min, 75 | new tarsMonitor.POLICY.Avg]); 76 | 77 | activeRequests = tarsMonitor.create(moduleName + '.activeRequests', [new tarsMonitor.POLICY.Max, 78 | new tarsMonitor.POLICY.Min, 79 | new tarsMonitor.POLICY.Avg]); 80 | }; 81 | 82 | var initLag = function() { 83 | eventloop = lag(exports.lagInterval); 84 | }; 85 | 86 | var reportCpu = function() { 87 | pidusage.stat(process.pid, function(err, stat) { 88 | if (!err) { 89 | cpuUsage.report(stat.cpu.toFixed(3)); 90 | } 91 | }); 92 | }; 93 | 94 | var reportMem = function() { 95 | var mem = process.memoryUsage(); 96 | 97 | rss.report(mem.rss); 98 | heapTotal.report(mem.heapTotal); 99 | heapUsed.report(mem.heapUsed); 100 | }; 101 | 102 | var reportLag = function() { 103 | eventloopLag.report(eventloop().toFixed(3)); 104 | }; 105 | 106 | var reportLibuv = function() { 107 | activeHandles.report(process._getActiveHandles().length); 108 | activeRequests.report(process._getActiveRequests().length); 109 | }; 110 | 111 | exports.init = function(obj) { 112 | initConfig(obj); 113 | initReporter() 114 | initLag(); 115 | }; 116 | 117 | exports.start = function() { 118 | if (!rss) { 119 | exports.init(); 120 | } 121 | 122 | if (!memTimerId) { 123 | memTimerId = setInterval(reportMem, exports.memInterval); 124 | memTimerId.unref(); 125 | } 126 | 127 | if (!lagTimerId) { 128 | lagTimerId = setInterval(reportLag, exports.lagInterval); 129 | lagTimerId.unref(); 130 | } 131 | 132 | if (!cpuTimerId) { 133 | cpuTimerId = setInterval(reportCpu, exports.cpuInterval); 134 | cpuTimerId.unref(); 135 | } 136 | 137 | if (!libuvTimerId) { 138 | libuvTimerId = setInterval(reportLibuv, exports.libuvInterval); 139 | libuvTimerId.unref(); 140 | } 141 | }; 142 | 143 | exports.stop = function() { 144 | if (memTimerId) { 145 | clearInterval(memTimerId); 146 | memTimerId = undefined; 147 | } 148 | 149 | if (lagTimerId) { 150 | clearInterval(lagTimerId); 151 | lagTimerId = undefined; 152 | } 153 | 154 | if (cpuTimerId) { 155 | clearInterval(cpuTimerId); 156 | cpuTimerId = undefined; 157 | } 158 | 159 | pidusage.unmonitor(process.pid); 160 | 161 | if (libuvTimerId) { 162 | clearInterval(libuvTimerId); 163 | libuvTimerId = undefined; 164 | } 165 | }; 166 | 167 | exports.lagInterval = 2 * 1000; 168 | exports.memInterval = 5 * 1000; 169 | exports.cpuInterval = 5 * 1000; 170 | exports.libuvInterval = 10 * 1000; -------------------------------------------------------------------------------- /lib/util/convert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var util = require('util'); 20 | 21 | exports.byteformat = function(length) { 22 | var unit, size; 23 | 24 | if (typeof length !== 'number' || isNaN(length) || length < 0) { 25 | throw new Error('input NOT valid'); 26 | } 27 | 28 | if (length < 103) { 29 | size = length; 30 | unit = ''; 31 | } else if (length < 1048576) { 32 | size = length / 1024; 33 | unit = 'K'; 34 | } else if (length < 1073741824) { 35 | size = length / 1048576; 36 | unit = 'M'; 37 | } else { 38 | size = length / 1073741824; 39 | unit = 'G'; 40 | } 41 | 42 | return size.toFixed(2).replace('.00', '') + unit; 43 | }; 44 | 45 | exports.friendlyformat = function(str) { 46 | var unit, size; 47 | 48 | if (typeof str !== 'string' || str === '') { 49 | throw new Error('input NOT String or it\'s empty'); 50 | } 51 | 52 | unit = str.toUpperCase().slice(-1); 53 | switch(unit) { 54 | case 'K' : { 55 | size = parseFloat(str.slice(0, str.length - 1)) * 1024; 56 | break; 57 | } 58 | case 'M' : { 59 | size = parseFloat(str.slice(0, str.length - 1)) * 1024 * 1024; 60 | break; 61 | } 62 | case 'G' : { 63 | size = parseFloat(str.slice(0, str.length - 1)) * 1024 * 1024 * 1024; 64 | break; 65 | } 66 | default : { 67 | size = parseFloat(str); 68 | } 69 | } 70 | 71 | if (isNaN(size)) { 72 | throw new Error('Can\'t convert to real size'); 73 | } 74 | if (size < 0) { 75 | throw new Error('Size Less then 0'); 76 | } 77 | 78 | return Math.round(size); 79 | }; 80 | 81 | exports.camelcase = function(flag) { 82 | return flag.split('-').reduce(function(str, word){ 83 | return str + word[0].toUpperCase() + word.slice(1); 84 | }); 85 | }; 86 | 87 | exports.extractAddress = function(addr) { 88 | addr = addr.split(':'); 89 | if (addr.length !== 2) { 90 | return null; 91 | } 92 | 93 | addr[1] = parseInt(addr[1]); 94 | 95 | if (isNaN(addr[1]) || addr[1] > 65535 || addr[1] < 1) { 96 | return null; 97 | } 98 | 99 | return { 100 | ip : addr[0], 101 | port : addr[1] 102 | }; 103 | }; 104 | 105 | exports.friendlyExit = function(code, signal, str) { 106 | if (typeof code === 'number') { 107 | return util.format('%s code 0x%s', str ? str : '', code.toString(16)); 108 | } else if (signal) { 109 | return util.format('%s signal %s', str ? str : '', signal); 110 | } else { 111 | return ''; 112 | } 113 | }; -------------------------------------------------------------------------------- /lib/util/cpu/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var os = require('os'); 20 | 21 | var cpuinfo; 22 | 23 | if (os.platform() === 'win32') { 24 | cpuinfo = require('./win'); 25 | } else { 26 | cpuinfo = require('./posix'); 27 | } 28 | 29 | exports.init = function(cb) { 30 | cpuinfo.init(function(err) { 31 | if (err) { 32 | cpuinfo = require('./system'); 33 | } 34 | 35 | exports.__defineGetter__('physicalCores', function() { 36 | return cpuinfo.physicalCores; 37 | }); 38 | 39 | exports.__defineGetter__('totalCores', function() { 40 | return cpuinfo.totalCores; 41 | }); 42 | 43 | cb(err); 44 | }); 45 | }; -------------------------------------------------------------------------------- /lib/util/cpu/posix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var assert = require('assert'); 20 | var fs = require('fs'); 21 | 22 | var cpuinfo = []; 23 | 24 | exports.init = function(cb) { 25 | fs.readFile('/proc/cpuinfo', {encoding : 'ascii'}, function(err, data) { 26 | var result = {}; 27 | 28 | if (err) { 29 | cb(new Error('get /proc/cpuinfo failed')); 30 | return; 31 | } 32 | 33 | data.split('\n').forEach(function(line) { 34 | line = line.replace(/\t/g, ''); 35 | 36 | var parts = line.split(':'); 37 | if (parts.length === 2) { 38 | result[parts[0].replace(/\s/g, '_')] = parts[1].trim().split(' ', 1)[0]; 39 | } 40 | 41 | if(line.length < 1) { 42 | cpuinfo.push(result); 43 | result = {}; 44 | } 45 | }); 46 | 47 | cpuinfo.pop(); 48 | 49 | if (cpuinfo.length === 0) { 50 | cb(new Error('parse cpuinfo data err')); 51 | } else { 52 | cb(); 53 | } 54 | }); 55 | }; 56 | 57 | exports.__defineGetter__('physicalCores', function() { 58 | var count = 0, phyidList = []; 59 | 60 | assert(cpuinfo.length !== 0, 'init MUST BE called first!'); 61 | 62 | cpuinfo.forEach(function(info) { 63 | if (phyidList.indexOf(info['physical_id']) === -1) { 64 | count += parseInt(info['cpu_cores']); 65 | phyidList.push(info['physical_id']); 66 | } 67 | }); 68 | 69 | return count; 70 | }); 71 | 72 | exports.__defineGetter__('totalCores', function() { 73 | assert(cpuinfo.length !== 0, 'init MUST BE called first!'); 74 | 75 | return cpuinfo.length; 76 | }); 77 | -------------------------------------------------------------------------------- /lib/util/cpu/system.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var os = require('os'); 20 | 21 | exports.__defineGetter__('physicalCores', function() { 22 | return 0; 23 | }); 24 | 25 | exports.__defineGetter__('totalCores', function() { 26 | return os.cpus() ? os.cpus().length : 1; 27 | }); -------------------------------------------------------------------------------- /lib/util/cpu/win.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var assert = require('assert'); 20 | 21 | var wmic = require('wmic'); 22 | 23 | var physicalCores, totalCores; 24 | 25 | exports.init = function(cb) { 26 | wmic.get_values('cpu', 'NumberOfCores, NumberOfLogicalProcessors', null, function(err, result) { 27 | if (err) { 28 | cb(new Error('exec wmic failed')); 29 | return; 30 | } 31 | 32 | physicalCores = parseInt(result.NumberOfCores); 33 | totalCores = parseInt(result.NumberOfLogicalProcessors); 34 | 35 | if (isNaN(physicalCores) || isNaN(totalCores) || physicalCores <= 0 || totalCores <= 0) { 36 | cb(new Error('parse cpuinfo data err')); 37 | } else { 38 | cb(); 39 | } 40 | }); 41 | }; 42 | 43 | exports.__defineGetter__('physicalCores', function() { 44 | assert(physicalCores > 0, 'init MUST BE called first!'); 45 | 46 | return physicalCores; 47 | }); 48 | 49 | exports.__defineGetter__('totalCores', function() { 50 | assert(totalCores > 0, 'init MUST BE called first!'); 51 | 52 | return totalCores; 53 | }); -------------------------------------------------------------------------------- /lib/util/lsdeps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var util = require('util'); 20 | var path = require('path'); 21 | 22 | var readInstalled = require("read-installed"); 23 | 24 | exports.list = function(cb) { 25 | readInstalled(path.join(__dirname, '../../'), {depth : 1}, function(err, data) { 26 | if (err) { 27 | cb(err); 28 | return; 29 | } 30 | 31 | cb(null, Object.getOwnPropertyNames(data.dependencies).map(function(name) { 32 | var deps = data.dependencies[name], depsStr; 33 | 34 | if (deps.dependencies) { 35 | depsStr = Object.getOwnPropertyNames(deps.dependencies).map(function(name) { 36 | return name + '@' + deps.dependencies[name].version; 37 | }).join(', '); 38 | } 39 | 40 | return util.format('%s@%s%s', name, deps.version, depsStr ? ' (' + depsStr + ')' : ''); 41 | }).join(', ')); 42 | }); 43 | }; -------------------------------------------------------------------------------- /lib/util/treekill.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Tars available. 3 | * 4 | * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/BSD-3-Clause 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed 12 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | * specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var childProcess = require('child_process'); 20 | var exec = childProcess.exec; 21 | var isWindows = process.platform === 'win32'; 22 | var downgradePs = false; 23 | 24 | module.exports = function (pid, signal, callback) { 25 | if (isWindows) { 26 | exec('taskkill /pid ' + pid + ' /T /F', callback); 27 | } else { 28 | var tree = {}; 29 | var pidsToProcess = {}; 30 | 31 | buildProcessTree(pid, tree, pidsToProcess, function () { 32 | try { 33 | killAll(tree, signal); 34 | } 35 | catch (err) { 36 | if (callback) { 37 | return callback(err); 38 | } else { 39 | throw err; 40 | } 41 | } 42 | if (callback) { 43 | return callback(); 44 | } 45 | }); 46 | } 47 | } 48 | 49 | function killAll (tree, signal) { 50 | var killed = {}; 51 | Object.keys(tree).forEach(function (pid) { 52 | tree[pid].forEach(function (pidpid) { 53 | if (!killed[pidpid]) { 54 | killPid(pidpid, signal); 55 | killed[pidpid] = 1; 56 | } 57 | }); 58 | if (!killed[pid]) { 59 | killPid(pid, signal); 60 | killed[pid] = 1; 61 | } 62 | }); 63 | } 64 | 65 | function killPid(pid, signal) { 66 | try { 67 | process.kill(parseInt(pid, 10), signal); 68 | } 69 | catch (err) { 70 | if (err.code !== 'ESRCH') throw err; 71 | } 72 | } 73 | 74 | function buildProcessTree(ppid, tree, pidsToProcess, cb) { 75 | pidsToProcess[ppid] = 1; 76 | tree[ppid] = []; 77 | 78 | function isFinish(){ 79 | delete pidsToProcess[ppid]; 80 | if(Object.keys(pidsToProcess).length == 0){ 81 | return cb(); 82 | } 83 | } 84 | 85 | var args = downgradePs ? '-eo pid,ppid | grep -w ' : '-o pid --no-headers --ppid '; 86 | exec('ps ' + args + ppid, function(err, stdout){ 87 | if (err) { 88 | // illegal option --, try to use basic `ps` instead of it. 89 | if (/illegal/.test(err.message) && !downgradePs) { 90 | downgradePs = true; 91 | return buildProcessTree(ppid, tree, pidsToProcess, cb); 92 | } 93 | 94 | // Avoid pipe close error - dynamic self-closing process. 95 | if(/Command failed/.test(err.message)) { 96 | return isFinish(); 97 | } 98 | throw err; 99 | } 100 | 101 | var pids = stdout.split('\n'); 102 | 103 | // remove parentPid if necessary. 104 | downgradePs && pids.shift(); 105 | 106 | pids = pids.filter(function(pid){ 107 | return !!pid; 108 | }).map(function(pid){ 109 | pid = pid.trim(); 110 | return parseInt(downgradePs ? pid.slice(0, pid.search(/\s/)) : pid, 10); 111 | }); 112 | 113 | if(pids.length > 0){ 114 | tree[ppid] = tree[ppid].concat(pids); 115 | pids.forEach(function(pid){ 116 | if(!tree[pid]) { 117 | buildProcessTree(pid, tree, pidsToProcess, cb); 118 | }else{ 119 | delete pidsToProcess[pid]; 120 | } 121 | }); 122 | } 123 | 124 | isFinish(); 125 | }); 126 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tars/node-agent", 3 | "preferGlobal": "true", 4 | "version": "2.1.3", 5 | "description": "TARS 框架中 Node.js 程序启动器,提供生产环境所需的服务特性", 6 | "author": { 7 | "name": "Superzheng", 8 | "email": "superzheng@tencent.com" 9 | }, 10 | "homepage": "https://github.com/tars-node/node-agent", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/tars-node/node-agent.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/tars-node/node-agent/issues" 17 | }, 18 | "keywords": [ 19 | "node-agent", 20 | "node", 21 | "tars" 22 | ], 23 | "dependencies": { 24 | "@tars/stream": "latest", 25 | "@tars/rpc": "latest", 26 | "@tars/monitor": "latest", 27 | "@tars/notify": "latest", 28 | "@tars/utils": "latest", 29 | "@tars/winston-tars": "latest", 30 | "commander": "2.9.0", 31 | "chalk": "1.1.3", 32 | "winston": "2.1.1", 33 | "callsite": "1.0.0", 34 | "wmic": "0.0.7", 35 | "read-installed": "4.0.3", 36 | "on-finished": "2.3.0", 37 | "event-loop-lag": "1.1.0", 38 | "pidusage": "1.0.4", 39 | "compare-versions": "3.3.0", 40 | "longstack": "1.0.0" 41 | }, 42 | "bin": { 43 | "node-agent": "./bin/node-agent" 44 | }, 45 | "license" : "BSD-3-Clause" 46 | } 47 | --------------------------------------------------------------------------------