Merge remote-tracking branch 'origin/development'

# Conflicts:
#	README.md
This commit is contained in:
vince 2018-07-03 14:42:11 +02:00
commit 29cccd9437
300 changed files with 6434 additions and 14672 deletions

View File

@ -2,10 +2,10 @@
"app_name": "",
"logo": "",
"intro": "",
"branch" : "master",
"branch" : "development",
"repo_url": "https://github.com/Unitech/pm2",
"version_name" : "2.10.4",
"tag": "2.10.3",
"version_name" : "3.0.0",
"tag": "2.10.4",
"file": "currentTagChangelog.md",
"template": "changelogTemplate.md",
"sections": [

2
.gitignore vendored
View File

@ -15,3 +15,5 @@ package-lock.json
*.swp
*.swo
currentTagChangelog.md
joblog-X
test/fixtures/path-check*.txt

View File

@ -4,14 +4,16 @@ node_js:
- "4"
- "6"
- "8"
- "0.12"
git:
depth: 5
os:
- linux
before_install:
- sudo apt-get -qq update
- sudo apt-get install parallel
- sudo apt-get install python3
- sudo apt-get install php5-cli
services:
- docker
notifications:
slack: pm2-nodejs:5Lolyw2LMnwy8fziqOGILQxG
install:
- npm install

View File

@ -32,7 +32,7 @@
- [Without Keymetrics](#without-keymetrics)
- [With Keymetrics](#with-keymetrics)
### Deployment - ecosystem.json
### Deployment - ecosystem.config.js
- [Getting started with deployment](#deployment)
- [Deployment options](#deployment-help)

View File

@ -1,3 +1,312 @@
## 3.0.0 ( Wed Jun 20 2018 11:06:21 GMT+0200 (CEST) )
## Breaking changes
- merge_logs is now activated by default if not in cluster mode. Logs will not be suffixed by the pm_id if only one app is started
([ae02adf6](https://github.com/Unitech/pm2/commit/ae02adf63f70ceb3bf101be968996ca68d9ce277))
- Drop support for node 0.12
- Drop gracefulReload command
- Remove Interactor from PM2 source code
- Replace pmx with [pm2-io-apm](https://github.com/keymetrics/pm2-io-apm)
## Bug Fixes
- return the configuration and allow custom conf to override default values
([37dc7de1](https://github.com/Unitech/pm2/commit/37dc7de11e930aa4fce6a485e892f11ee714acd6))
- add use strict for node 4 compatibility
([ba2ee3b1](https://github.com/Unitech/pm2/commit/ba2ee3b1ea9aa5fa665e706b3d49a205eac44d53))
- #3605 fix parameters definition, don't use camelcase for properties
([c8616276](https://github.com/Unitech/pm2/commit/c8616276e4e08b4d90a742e219372e775bb81098))
- #3695 change version check method in order to make it work with alpha/beta versions
([052d6c55](https://github.com/Unitech/pm2/commit/052d6c55df0e941e1dd11430bbcbcaa34061a06e))
- deprecated warning on isbinaryfile
([db09275f](https://github.com/Unitech/pm2/commit/db09275f8e353e257c89e12fed754236b15cee74))
- #3688 test adaptation + pm2 serve --port option
([f0249684](https://github.com/Unitech/pm2/commit/f0249684bcbfdb75749a516f447c8e8d32020709))
- startup script issue 18.04 #3645
([ff1a7f31](https://github.com/Unitech/pm2/commit/ff1a7f315bfee38eb9fd9cdd63efcc0d971585f8))
- that this - uncache node_modules
([294038d7](https://github.com/Unitech/pm2/commit/294038d76272a915e3addc67d3694717a9f7d704))
- verify default conf variable via package.json on public module
([157b106d](https://github.com/Unitech/pm2/commit/157b106df78af1d28d37bbea069b926de4dceca5))
- bug because of const
([56f05a90](https://github.com/Unitech/pm2/commit/56f05a900b03fb0c8dd635aede666c7d2f213271))
- do not run two pm2 para cmds
([3274132b](https://github.com/Unitech/pm2/commit/3274132b866ba5c93d5786e755acbada922f5f1e))
- version
([3ec178e5](https://github.com/Unitech/pm2/commit/3ec178e577e79730aae02c913301cd905ea8ce52))
- re-enable agent tests
([e6febcd7](https://github.com/Unitech/pm2/commit/e6febcd70dd0f1e68b74df8563d3046ee3b32b89))
- test/display summary
([b075e6d0](https://github.com/Unitech/pm2/commit/b075e6d09b09ff371adf045dc5079bb8ef82f1cf))
- skip interactor tests
([36c4d6bc](https://github.com/Unitech/pm2/commit/36c4d6bca7445b46afc1236dc8ab4b8bf921148b))
- remove unused tests
([234c6314](https://github.com/Unitech/pm2/commit/234c63143e723a508796bc1d323c7241979bf4c2))
- add missing libraries in travis
([88fbb845](https://github.com/Unitech/pm2/commit/88fbb84597cee7029ce33f5b7e20e45f5a815b4b))
- remove unused variable when trying to use tracing
([3aeeba02](https://github.com/Unitech/pm2/commit/3aeeba02f628bf4f19e8d5b93657fd94a6ef0ec7))
- remove useless tests from .sh
([e0be81c8](https://github.com/Unitech/pm2/commit/e0be81c86c7defb5e7a271edd5cc37f960c6aa69))
- conflict
([e13f39c9](https://github.com/Unitech/pm2/commit/e13f39c90b6a5e803c59c5424332520564703f5c))
- fix bug with interpreter args
([b26efa0d](https://github.com/Unitech/pm2/commit/b26efa0d4cd72cf04762df7b7d2eaddc4f4117d2))
- improve error message if action has failed
([d9f44f17](https://github.com/Unitech/pm2/commit/d9f44f170f115c2d6dfb6a7fe71dc31bd7fb66fb))
- use polyfill module for copySync with node 4.x
([bc07f43b](https://github.com/Unitech/pm2/commit/bc07f43b115066f6077606df8f59379777f2a917))
- improve error message if action has failed
([dacc6542](https://github.com/Unitech/pm2/commit/dacc654207cbe494af0d12a3f9f27c3b16541802))
- solve empty list when no process and try to update pm2
([89511846](https://github.com/Unitech/pm2/commit/8951184688c720ded5b4b46bd5b393c3793f9b03))
- #3485 fix issue when there is empty dump file
([f2523f6a](https://github.com/Unitech/pm2/commit/f2523f6a6b9d8b61ba6ace7b89a0353bee76360b))
- #3456 use homedir() instead of process.env.HOME, make module installation work on windows
([1e001732](https://github.com/Unitech/pm2/commit/1e0017325fc8cf658263fb4e02c7bf8912f422b3))
## Features
- add support for openbsd rc.d init scripts
([fdeb0c32](https://github.com/Unitech/pm2/commit/fdeb0c327afd91b113b214c4c4de187848f9f1cb))
- add kill_retry_time argument
([b2cc0031](https://github.com/Unitech/pm2/commit/b2cc003114b44f1a9a31876ee4a2f4cb91e210b3))
- **bin/pm2**
- improve usage
([2c310084](https://github.com/Unitech/pm2/commit/2c310084453dd7b1546957e59b1fc7ef964d425b))
## Refactor
- use @pm2/js-api for login/register on pm2.io via CLI
([cb6521ac](https://github.com/Unitech/pm2/commit/cb6521ac32f4737c42fc97fef972960bfe16c829))
- keymetrics examples
([109b331d](https://github.com/Unitech/pm2/commit/109b331ddf37e061d1890ef952f4cd167ce53f64))
- faster cli with less require
([ee5e6a06](https://github.com/Unitech/pm2/commit/ee5e6a06cbf93f2d1fa7fa022d6bdcad55a39695))
- replace fs-extra with node calls
([4576b4c9](https://github.com/Unitech/pm2/commit/4576b4c97bc685c9d774018d6b29c918abd7cb8d))
- centralize SECRET/PUBLIC/MACHINE_NAME + change some wordings
([d0a2a30e](https://github.com/Unitech/pm2/commit/d0a2a30e4110496b178199fb33e026d6402dd00d))
- remove test deported to keymetrics-agent
([299a52a2](https://github.com/Unitech/pm2/commit/299a52a253d70edcde23cbd7e0c201d492984df4))
- parallel test v1
([08612de5](https://github.com/Unitech/pm2/commit/08612de5b7893a004ae33ed77fcb2ee3ff7b2251))
- e2e test rewrite
([2b9ffd4e](https://github.com/Unitech/pm2/commit/2b9ffd4eb493f1ff32c979e3811f4f1fedfae97d))
- drop gracefullreload
([bb57c76d](https://github.com/Unitech/pm2/commit/bb57c76d4191343925013d4353299092d80732c9))
- add node 4.x support
([d322dd00](https://github.com/Unitech/pm2/commit/d322dd00de0f527224c027b4fec5e86f12fd69ed))
- create alias method instead of modify prototype
([6d8f0dfa](https://github.com/Unitech/pm2/commit/6d8f0dfae8106deb2fee0a7ae15b6ca9802a066d))
- change safety var to const
([047aa494](https://github.com/Unitech/pm2/commit/047aa494d5c4dd4342915766b54d673db0d5cdf1))
- drop some 0.x patch
([0cab8880](https://github.com/Unitech/pm2/commit/0cab8880ffa362cf27ab7d7b6a64d6b478dce7cd))
- remove prototype from API and create method
([9552bd61](https://github.com/Unitech/pm2/commit/9552bd61b72692beb620a91765ad440cdf6abefe))
- transform API into class
([e3831f95](https://github.com/Unitech/pm2/commit/e3831f95c8d71f98e8840da37f7e883727eccd59))
- name tests well
([c3ccc651](https://github.com/Unitech/pm2/commit/c3ccc651d09ed7291090f516637b75bda99ff71c))
- refactor e2e one line parallel
([93802711](https://github.com/Unitech/pm2/commit/938027117cdb2f300ee772ab27f008cbe22a4b19))
- e2e rename
([8a7db95a](https://github.com/Unitech/pm2/commit/8a7db95aabc8437f292af0316cec81ab80ec41f5))
- change params
([282186f2](https://github.com/Unitech/pm2/commit/282186f24b19b010999f7c7c49750935ef19c190))
- parallelize bash test
([d4b4375e](https://github.com/Unitech/pm2/commit/d4b4375e16fe7ac463b252702da662d3a21bf8b4))
## Test
- adapt test to new api
([7a275e27](https://github.com/Unitech/pm2/commit/7a275e279ea01b1239e9dd8b9cf8e088e407b96d))
- refactor before/after
([b85ca3ca](https://github.com/Unitech/pm2/commit/b85ca3caa3c68e18f7ce6954cc85e90a9d33efef))
- 3 concurrent jobs
([472aba34](https://github.com/Unitech/pm2/commit/472aba3499ff2d9d0eb834e819410026b1a44503))
- move test
([9c973324](https://github.com/Unitech/pm2/commit/9c9733246dbe6afff1b488bc3ba3b6fea3877ea5))
- move test
([952b7631](https://github.com/Unitech/pm2/commit/952b7631d19e1074ea73cc7a67bbaefe20950603))
- fix test with km_link
([23fd8ecf](https://github.com/Unitech/pm2/commit/23fd8ecfea9b2bf61359f62a8e6e1a582c3b0d6e))
## Chore
- shorten ecosystem file
([992a0452](https://github.com/Unitech/pm2/commit/992a045227aed559e708ac4e6bb3f54beabe48e0))
- change motd wording
([aa183ba1](https://github.com/Unitech/pm2/commit/aa183ba19d88777d82619aa40499c2661d67879e))
- merge master in development
([0e4453d9](https://github.com/Unitech/pm2/commit/0e4453d9cc789aa08ee778ff400572337e90d2e3))
- keymetrics -> pm2
([2c8170c2](https://github.com/Unitech/pm2/commit/2c8170c25e231eb8827bb0944b76c2f4b041d84e))
- upgrade all modules + keymetrics-agent -> pm2/agent + increase version enabling v8-compile-cache
([53ca18c1](https://github.com/Unitech/pm2/commit/53ca18c12868ab177b60a4edff2ccaa8127e301f))
- pm2.io -> @pm2/io
([ae098962](https://github.com/Unitech/pm2/commit/ae098962df35eee7f482dc0a514fd29a02a5f4ad))
- right names as pm2 maintainers
([e8cd7131](https://github.com/Unitech/pm2/commit/e8cd7131a6b9c9d497a2079bcbfc03770a753a06))
- add changelog generation into contributing.md
([d77bfbc3](https://github.com/Unitech/pm2/commit/d77bfbc3c8929851ee19ea604b2a6481d03771e3))
- cache node_modules
([81627e94](https://github.com/Unitech/pm2/commit/81627e94c72efa1f4d726e20bbf67f0bbd5c116f))
- clone last 5 commits
([dad38ed1](https://github.com/Unitech/pm2/commit/dad38ed1bae849147f66e44186cd71c4b9cb022d))
- delete old stagnating pmx inside test
([36834c2c](https://github.com/Unitech/pm2/commit/36834c2c00d496e04c38abaca30202eb650015c4))
- pmx -> pm2.io
([adcbebc3](https://github.com/Unitech/pm2/commit/adcbebc3f6419cd97c5ea99f3c3a6789585bda66))
- updgrade pmx-2
([eeeb2988](https://github.com/Unitech/pm2/commit/eeeb2988f8886e405aea107db3b888fc1fc929f8))
- disable legacy test
([13723bd9](https://github.com/Unitech/pm2/commit/13723bd938d0e6fb1cbf35f15eabe91c52d87b58))
- remove test for pmx alert system
([c43414a6](https://github.com/Unitech/pm2/commit/c43414a63438d724b8099eb531ec72bab23b8ca2))
- sync from master
([3424ee27](https://github.com/Unitech/pm2/commit/3424ee27870feaf62fdf4509cce9015f8b1a8a2e))
- add unique id for each process
([85a5ee0f](https://github.com/Unitech/pm2/commit/85a5ee0f1fd16da9635fb4b16ddcd8d53aca8224))
- use npm install for CI as yarn has issue with npm
([52902186](https://github.com/Unitech/pm2/commit/5290218626af815f6cae8173bc78d21881a4dda8))
- remove unused dependency
([830fc15f](https://github.com/Unitech/pm2/commit/830fc15fad1aee95e65b2681482b03369f1f97d7))
- upgrade PM2 to 3.0
([4bc2eb4c](https://github.com/Unitech/pm2/commit/4bc2eb4c9a8179b9ae38438e98ce7650a91b64db))
- remove unused console.log
([33db5084](https://github.com/Unitech/pm2/commit/33db5084814ae7940c90b7f933f9514d28008b78))
- wording on error message
([c251c8c9](https://github.com/Unitech/pm2/commit/c251c8c97e6f18aae584cac6b7f3c83cf4f2de9c))
- revert PR #3496
([aae1d55e](https://github.com/Unitech/pm2/commit/aae1d55e410c4dcfbbca83eaabbdf1a65d55f3aa))
- fix issue with snapshot command + remove command forceGc
([97fd1010](https://github.com/Unitech/pm2/commit/97fd1010d005e59f2411042fa95891f9717fa8b7))
- wording on error message
([5f78ecbf](https://github.com/Unitech/pm2/commit/5f78ecbf90f9f46a7feb2a169968e86b0ecac91e))
- drop 0.12 test on travis
([beb6e487](https://github.com/Unitech/pm2/commit/beb6e48787c39c66569141d0fd8d090736114d23))
- downgrade promptly
([074a7a40](https://github.com/Unitech/pm2/commit/074a7a407a31b4d88442f5834d253d62f4e543b8))
- remove coffee and livescript dependencies
([13d6565c](https://github.com/Unitech/pm2/commit/13d6565c72e3596d05f87bfc8be15d3ee45fb279))
- upgrade module version and engine version
([84796956](https://github.com/Unitech/pm2/commit/84796956347ca638750fe89cb5545e2a90a0f2c2))
## Branchs merged
- Merge branch 'development' into chore/dev-cache-node-modules
([146c4e11](https://github.com/Unitech/pm2/commit/146c4e113c88e8ade17c7558c8e14cf523a3b2d6))
- Merge branch 'development' of https://github.com/Unitech/pm2 into new-agent
([3514e7fa](https://github.com/Unitech/pm2/commit/3514e7fac624bb83b4cc22651ebc05385f9c284d))
- Merge branch 'development' into master
([f5668331](https://github.com/Unitech/pm2/commit/f5668331dbe7346304258317a3b84450f421ed03))
- Merge branch 'development' into new-usage-cli
([4ae27694](https://github.com/Unitech/pm2/commit/4ae27694e34c4bc6ed389566d71fc5ec48b69652))
- Merge branch 'Eywek-improv/agent' into new-agent
([3e259dd1](https://github.com/Unitech/pm2/commit/3e259dd1d6bb96ea41897c49f3a84557c00c7dad))
- Merge branch 'ecosystem-documentation' of github.com:rmonnier/pm2 into ecosystem-documentation
([98348955](https://github.com/Unitech/pm2/commit/98348955a6eb3a9cd524b991bd1dd6ed03d2c857))
- Merge branch 'development' into ecosystem-documentation
([40157784](https://github.com/Unitech/pm2/commit/40157784a63bcb0e744d4ed56f6c687e28379fdd))
- Merge branch 'inspect_mode' of github.com:Unitech/pm2 into inspect_mode
([7e1494c7](https://github.com/Unitech/pm2/commit/7e1494c7f7971aaf1f4d00d2ee691c3c41775001))
- Merge branch 'development' of github.com:Unitech/pm2 into development
([48f81a8b](https://github.com/Unitech/pm2/commit/48f81a8b2f6f0db39edd86083fb369b74845c387))
- Merge branch 'development' into master
([47e54109](https://github.com/Unitech/pm2/commit/47e5410987ab3d824a34c062d70c24ab686e57db))
- Merge branch 'development' into module_install_windows
([7b82fb91](https://github.com/Unitech/pm2/commit/7b82fb916ed453c1c263bae43c962f6a5294d810))
- Merge branch 'development' into module_install_windows
([80b0495f](https://github.com/Unitech/pm2/commit/80b0495f63d1224b850af4b14cdeb055e3fef50b))
## Pull requests merged
- Merge pull request #3726 from soyuka/fix-list
([0255c5a6](https://github.com/Unitech/pm2/commit/0255c5a6ab1b8a8f609d2183d998695b8c42838d))
- Merge pull request #3725 from soyuka/fix-list
([a39eb4f8](https://github.com/Unitech/pm2/commit/a39eb4f806e87565f53758a19f0ee289b6489b67))
- Merge pull request #3718 from AaronM04/openbsd-init-script
([85458261](https://github.com/Unitech/pm2/commit/85458261d2673c609cb252d64ad4dfbaa466d848))
- Merge pull request #3721 from Unitech/io_conf
([70ec1f81](https://github.com/Unitech/pm2/commit/70ec1f81eae089f75e82723fde7b0b3926d0a9bc))
- Merge pull request #3716 from Unitech/io_conf
([0bc000b9](https://github.com/Unitech/pm2/commit/0bc000b9aae7dd37b456bc2d4fbc9eb4a9f047ef))
- Merge pull request #3714 from Unitech/definition
([d8cff0de](https://github.com/Unitech/pm2/commit/d8cff0dec5160a620d1512ff56726c073368d1a4))
- Merge pull request #3700 from Unitech/report_error
([4b2cad40](https://github.com/Unitech/pm2/commit/4b2cad407b76994e978074a2a3825fe70656304d))
- Merge pull request #3670 from Unitech/changelog
([4bcbcce1](https://github.com/Unitech/pm2/commit/4bcbcce16ced596f6ca2bab2b77d608a174a7c1a))
- Merge pull request #3662 from DanielRuf/chore/dev-cache-node-modules
([540590ee](https://github.com/Unitech/pm2/commit/540590ee056b44eed3b688a7b0b16ca78ec82cd9))
- Merge pull request #3663 from DanielRuf/chore/dev-clone-last-5-commits
([bdf95fc9](https://github.com/Unitech/pm2/commit/bdf95fc997f9ab2995b23668f25f11b6e98b5c47))
- Merge pull request #3584 from ngtmuzi/development
([33984b64](https://github.com/Unitech/pm2/commit/33984b64a2969ca4a3a5913f0f7da0242b6c5ec1))
- Merge pull request #3500 from Unitech/test-parallel
([da56c7af](https://github.com/Unitech/pm2/commit/da56c7aff18d3a38b3ad068b22cd75b290bac9d0))
- Merge pull request #3539 from KimSeongIl/master
([1325704d](https://github.com/Unitech/pm2/commit/1325704d95d324e56b0ebc86aed8137e0d0aa450))
- Merge pull request #3556 from N-Nagorny/logs-smart-app-name-cutting
([bfddf4fd](https://github.com/Unitech/pm2/commit/bfddf4fdef5ec293119d850cc2532ac5d6490ae3))
- Merge pull request #3553 from Unitech/fix_tracing_not_working
([9d51fe08](https://github.com/Unitech/pm2/commit/9d51fe0819182339f3a6a4aee7ea603ea3f4dd76))
- Merge pull request #3549 from Eywek/new-agent
([2f04027b](https://github.com/Unitech/pm2/commit/2f04027b536094d192b399677b3a113102f06b8e))
- Merge pull request #3548 from rmonnier/start-ecosystem-default
([55412f26](https://github.com/Unitech/pm2/commit/55412f263250395de0085144932cfe06b8c7180d))
- Merge pull request #3546 from soyuka/improve-monitor-perf
([e4e29233](https://github.com/Unitech/pm2/commit/e4e29233f99db36462a6e8f48eb8ebd3d2fd9fa5))
- Merge pull request #3534 from rmonnier/new-usage-cli
([5dfba8a4](https://github.com/Unitech/pm2/commit/5dfba8a4491f0bb83f2879915f0c4b164be2552c))
- Merge pull request #3542 from rmonnier/default-start-ecosystem
([c65595f4](https://github.com/Unitech/pm2/commit/c65595f4a70659e1e0d753e6c28a1fcedf45a91a))
- Merge pull request #3545 from rmonnier/default-ecosystem
([b3718656](https://github.com/Unitech/pm2/commit/b3718656f630aa54880343d9742534a2a508daec))
- Merge pull request #3543 from rmonnier/ecosystem-documentation
([a60580a1](https://github.com/Unitech/pm2/commit/a60580a12b4a0066c8df6620317fbc8bf599b0b6))
- Merge pull request #3541 from soyuka/development
([67e7a015](https://github.com/Unitech/pm2/commit/67e7a015cabaa7b08206a3b1bf9c0399af88f76b))
- Merge pull request #3511 from Unitech/inspect_mode
([75fb87f8](https://github.com/Unitech/pm2/commit/75fb87f8a1c46a6db8e974b421e857175e69b535))
- Merge pull request #3517 from Unitech/polyfill_fs_copy_node4
([524f5494](https://github.com/Unitech/pm2/commit/524f54948de5080632d43bb512038d7bd7271619))
- Merge pull request #3516 from Unitech/drop_unused_feature
([9436f11a](https://github.com/Unitech/pm2/commit/9436f11aeecfc07e77aa9d6b108df4478b43402e))
- Merge pull request #3510 from Unitech/dump_refacto
([674e4469](https://github.com/Unitech/pm2/commit/674e4469554e6a765bb3d57a3c083e6ab53b20cc))
- Merge pull request #3501 from Unitech/refactor_api
([9f2c4ca4](https://github.com/Unitech/pm2/commit/9f2c4ca4c9eadf6c7730e3889c72e908cd2d8f5d))
- Merge pull request #3496 from rmonnier/master
([829cc303](https://github.com/Unitech/pm2/commit/829cc3032b2d61e20f7a2e7d1d819c0ddc0845e8))
- Merge pull request #3484 from Unitech/pull_by_name
([24d29404](https://github.com/Unitech/pm2/commit/24d294049008a0d01b2bc407b9b2b880d5843fbd))
- Merge pull request #3482 from Unitech/mjs_support
([ebe7b048](https://github.com/Unitech/pm2/commit/ebe7b0487218557858aaa98527360eca1776b140))
- Merge pull request #3495 from Unitech/module_install_windows
([e9c625d3](https://github.com/Unitech/pm2/commit/e9c625d3088c71eef4237ecd866b806957c61815))
- Merge pull request #3507 from cheapsteak/patch-1
([a49287d6](https://github.com/Unitech/pm2/commit/a49287d6a1d22b39270e2d05dee2a17c0ed55797))
## 2.10.4 ( Thu May 17 2018 14:32:40 GMT+0200 (CEST) )

View File

@ -100,3 +100,25 @@ This test files are located in test/bash/*
- `$HOME/.pm2/pm2.pid` PM2 pid
- `$HOME/.pm2/rpc.sock` Socket file for remote commands
- `$HOME/.pm2/pub.sock` Socket file for publishable events
## Generate changelog
### requirements
```
npm install git-changelog -g
```
### usage
Edit .changelogrc
Change "version_name" to the next version to release (example 1.1.2).
Change "tag" to the latest existing tag (example 1.1.1).
Run the following command into pm2 directory
```
git-changelog
```
It will generate currentTagChangelog.md file.
Just copy/paste the result into changelog.md

View File

@ -17,7 +17,7 @@
<img src="https://img.shields.io/badge/node-%3E%3D4-brightgreen.svg" alt="npm version" height="18">
</a>
<a href="https://www.npmjs.com/package/pm2" title="PM2 on NPM">
<a href="https://npmcharts.com/compare/pm2?minimal=true" title="PM2 on NPM">
<img alt="NPM Downloads" src="https://img.shields.io/npm/dm/pm2.svg?style=flat-square"/>
</a>
@ -234,14 +234,14 @@ $ pm2 reloadLogs # Reload all logs
PM2 can generates and configure a startup script to keep PM2 and your processes alive at every server restart.
Supports init systems like: **systemd** (Ubuntu 16, CentOS, Arch), **upstart** (Ubuntu 14/12), **launchd** (MacOSx, Darwin), **rc.d** (FreeBSD).
Supports init systems like: **systemd** (Ubuntu 16, CentOS, Arch), **upstart** (Ubuntu 14/12), **launchd** (MacOSx, Darwin), **rc.d** (FreeBSD, OpenBSD).
```bash
# Auto detect init system + generate and setup PM2 boot at server startup
$ pm2 startup
# Manually specify the startup system
# Can be: systemd, upstart, launchd, rcd
# Can be: systemd, upstart, launchd, rcd, rcd-openbsd
$ pm2 startup [platform]
# Disable and remove PM2 boot at server startup
@ -294,7 +294,6 @@ $ pm2 reset [app-name] # Reset all counters
$ pm2 stop all # Stop all apps
$ pm2 stop 0 # Stop process with id 0
$ pm2 restart all # Restart all apps
$ pm2 gracefulReload all # Gracefully reload all apps in cluster mode
$ pm2 delete all # Kill and delete all apps
$ pm2 delete 0 # Delete app with id 0

202
bin/pm2
View File

@ -3,7 +3,7 @@
var semver = require('semver');
if (semver.satisfies(process.versions.node, '>= 5.7.0'))
if (semver.satisfies(process.versions.node, '>= 6.0.0'))
require('v8-compile-cache');
process.env.PM2_USAGE = 'CLI';
@ -33,24 +33,29 @@ if (process.argv.indexOf('-v') > -1) {
var pm2 = new PM2();
commander.version(pkg.version)
.option('-v --version', 'get version')
.option('-v --version', 'print pm2 version')
.option('-s --silent', 'hide all messages', false)
.option('-n --name <name>', 'set a name for the process in the process list')
.option('-m --mini-list', 'display a compacted list without formatting')
.option('--interpreter <interpreter>', 'set a specific interpreter to use for executing app, default: node')
.option('--interpreter-args <arguments>', 'set arguments to pass to the interpreter (alias of --node-args)')
.option('--node-args <node_args>', 'space delimited arguments to pass to node')
.option('-o --output <path>', 'specify log file for stdout')
.option('-e --error <path>', 'specify log file for stderr')
.option('-l --log [path]', 'specify log file which gathers both stdout and stderr')
.option('--log-type <type>', 'specify log output style (raw by default, json optional)')
.option('--log-date-format <date format>', 'add custom prefix timestamp to logs')
.option('--disable-logs', 'disable all logs storage')
.option('--env <environment_name>', 'specify which set of environment variables from ecosystem file must be injected')
.option('-a --update-env', 'force an update of the environment with restart/reload (-a <=> apply)')
.option('-f --force', 'force actions')
.option('--disable-logs', 'do not write logs')
.option('-n --name <name>', 'set a <name> for script')
.option('-i --instances <number>', 'launch [number] instances (for networked app)(load balanced)')
.option('--parallel <number>', 'number of parallel actions (for restart/reload)')
.option('-l --log [path]', 'specify entire log file (error and out are both included)')
.option('-o --output <path>', 'specify out log file')
.option('-e --error <path>', 'specify error log file')
.option('-p --pid <pid>', 'specify pid file')
.option('-k --kill-timeout <delay>', 'delay before sending final SIGKILL signal to process')
.option('--listen-timeout <delay>', 'listen timeout on application reload')
.option('--max-memory-restart <memory>', 'specify max memory amount used to autorestart (in octet or use syntax like 100M)')
.option('--max-memory-restart <memory>', 'Restart the app if an amount of memory is exceeded (in bytes)')
.option('--restart-delay <delay>', 'specify a delay between restarts (in milliseconds)')
.option('--env <environment_name>', 'specify environment to get specific env variables (for JSON declaration)')
.option('--log-type <type>', 'specify log output style (raw by default, json optional)')
.option('-x --execute-command', 'execute a program using fork system')
.option('--max-restarts [count]', 'only restart the script COUNT times')
.option('-u --user <username>', 'define user when generating startup script')
@ -62,19 +67,14 @@ commander.version(pkg.version)
.option('--service-name <name>', 'define service name when generating startup script')
.option('-c --cron <cron_pattern>', 'restart a running process based on a cron pattern')
.option('-w --write', 'write configuration in local folder')
.option('--interpreter <interpreter>', 'the interpreter pm2 should use for executing app (bash, python...)')
.option('--interpreter-args <arguments>', 'interpret options (alias of --node-args)')
.option('--log-date-format <date format>', 'add custom prefix timestamp to logs')
.option('--no-daemon', 'run pm2 daemon in the foreground if it doesn\'t exist already')
.option('-a --update-env', 'update environment on restart/reload (-a <=> apply)')
.option('--source-map-support', 'force source map support')
.option('--only <application-name>', 'with json declaration, allow to only act on one application')
.option('--disable-source-map-support', 'force source map support')
.option('--wait-ready', 'ask pm2 to wait for ready event from your app')
.option('--merge-logs', 'merge logs from different instances but keep error and out separated')
.option('--watch [paths]', 'watch application folder for changes', function(v, m) { m.push(v); return m;}, [])
.option('--ignore-watch <folders|files>', 'folder/files to be ignored watching, should be a specific name or regex - e.g. --ignore-watch="test node_modules \"some scripts\""')
.option('--node-args <node_args>', 'space delimited arguments to pass to node in cluster mode - e.g. --node-args="--debug=7001 --trace-deprecation"')
.option('--ignore-watch <folders|files>', 'List of paths to ignore (name or regex)')
.option('--no-color', 'skip colors')
.option('--no-vizion', 'start an app without vizion feature (versioning control)')
.option('--no-autorestart', 'start an app without automatic restart')
@ -90,38 +90,50 @@ commander.version(pkg.version)
.option('--deep-monitoring', 'enable all monitoring tools (equivalent to --v8 --event-loop-inspector --trace)')
.usage('[cmd] app');
commander.on('--help', function() {
console.log(' Basic Examples:');
console.log('');
console.log(' Start an app using all CPUs available + set a name :');
console.log(' $ pm2 start app.js -i 0 --name "api"');
console.log('');
console.log(' Restart the previous app launched, by name :');
console.log(' $ pm2 restart api');
console.log('');
console.log(' Stop the app :');
console.log(' $ pm2 stop api');
console.log('');
console.log(' Restart the app that is stopped :');
console.log(' $ pm2 restart api');
console.log('');
console.log(' Remove the app from the process list :');
console.log(' $ pm2 delete api');
console.log('');
console.log(' Kill daemon pm2 :');
console.log(' $ pm2 kill');
console.log('');
console.log(' Update pm2 :');
console.log(' $ npm install pm2@latest -g ; pm2 update');
console.log('');
console.log(' More examples in https://github.com/Unitech/pm2#usagefeatures');
console.log('');
console.log(' Deployment help:');
console.log('');
console.log(' $ pm2 deploy help');
console.log('');
console.log('');
});
function displayUsage() {
console.log('usage: pm2 [options] <command>')
console.log('');
console.log('pm2 -h, --help all available commands and options');
console.log('pm2 examples display pm2 usage examples');
console.log('pm2 <command> -h help on a specific command');
console.log('');
console.log('Access pm2 files in ~/.pm2');
}
function displayExamples() {
console.log('- Start and add a process to the pm2 process list:')
console.log('');
console.log(chalk.cyan(' $ pm2 start app.js --name app'));
console.log('');
console.log('- Show the process list:');
console.log('');
console.log(chalk.cyan(' $ pm2 ls'));
console.log('');
console.log('- Stop and delete a process from the pm2 process list:');
console.log('');
console.log(chalk.cyan(' $ pm2 delete app'));
console.log('');
console.log('- Stop, start and restart a process from the process list:');
console.log('');
console.log(chalk.cyan(' $ pm2 stop app'));
console.log(chalk.cyan(' $ pm2 start app'));
console.log(chalk.cyan(' $ pm2 restart app'));
console.log('');
console.log('- Clusterize an app to all CPU cores available:');
console.log('');
console.log(chalk.cyan(' $ pm2 start -i max'));
console.log('');
console.log('- Update pm2 :');
console.log('');
console.log(chalk.cyan(' $ npm install pm2 -g && pm2 update'));
console.log('');
console.log('- Install pm2 auto completion:')
console.log('');
console.log(chalk.cyan(' $ pm2 completion install'))
console.log('');
console.log('Check the full documentation on https://pm2.io/doc');
console.log('');
}
if (process.argv.indexOf('-s') > -1) {
for(var key in console){
@ -155,7 +167,7 @@ function checkCompletion(){
return data.short;
}), data);
// array containing commands after which process name should be listed
var cmdProcess = ['stop', 'restart', 'scale', 'reload', 'gracefulReload', 'delete', 'reset', 'pull', 'forward', 'backward', 'logs', 'describe', 'desc', 'show'];
var cmdProcess = ['stop', 'restart', 'scale', 'reload', 'delete', 'reset', 'pull', 'forward', 'backward', 'logs', 'describe', 'desc', 'show'];
if (cmdProcess.indexOf(data.prev) > -1) {
pm2.list(function(err, list){
@ -253,7 +265,7 @@ function patchCommanderArg(cmd) {
//
// Start command
//
commander.command('start <file|json|stdin|app_name|pm_id...>')
commander.command('start [name|file|ecosystem|id...]')
.option('--watch', 'Watch folder for changes')
.option('--fresh', 'Rebuild Dockerfile')
.option('--daemon', 'Run container in Daemon mode (debug purposes)')
@ -280,6 +292,9 @@ commander.command('start <file|json|stdin|app_name|pm_id...>')
else {
// Commander.js patch
cmd = patchCommanderArg(cmd);
if (cmd.length === 0) {
cmd = [cst.APP_CONF_DEFAULT_FILE];
}
async.forEachLimit(cmd, 1, function(script, next) {
pm2.start(script, commander, next);
}, function(err) {
@ -329,7 +344,7 @@ commander.command('startOrGracefulReload <json>')
//
commander.command('stop <id|name|all|json|stdin...>')
.option('--watch', 'Stop watching folder for changes')
.description('stop a process (to start it again, do pm2 restart <app>)')
.description('stop a process')
.action(function(param) {
async.forEachLimit(param, 1, function(script, next) {
pm2.stop(script, next);
@ -390,25 +405,24 @@ commander.command('reload <name|all>')
pm2.reload(pm2_id, commander);
});
//
// Reload process(es)
//
commander.command('gracefulReload <name|all>')
.description('gracefully reload a process. Send a "shutdown" message to close all connections.')
.action(function(pm2_id) {
pm2.gracefulReload(pm2_id, commander);
});
commander.command('id <name>')
.description('get process id by name')
.action(function(name) {
pm2.getProcessIdByName(name);
});
// Inspect a process
commander.command('inspect <name>')
.description('inspect a process')
.action(function(cmd) {
pm2.inspect(cmd, commander);
});
//
// Stop and delete a process by name from database
//
commander.command('delete <name|id|script|all|json|stdin...>')
.alias('del')
.description('stop and delete a process from pm2 process list')
.action(function(name) {
if (name == "-") {
@ -464,11 +478,11 @@ commander.command('update')
/**
* Module specifics
*/
commander.command('install [module|git:// url|json]')
commander.command('install <module|git:// url>')
.alias('module:install')
.option('--v1', 'install module in v1 manner (do not use it)')
.option('--safe [time]', 'keep module backup, if new module fail = restore with previous')
.description('install or update a module (or a set of modules) and run it forever')
.description('install or update a module and run it forever')
.action(function(plugin_name, opts) {
if (opts.v1)
commander.v1 = true;
@ -553,41 +567,42 @@ commander.command('report')
commander.command('link [secret] [public] [name]')
.alias('interact')
.option('--info-node [url]', 'set url info node')
.description('linking action to keymetrics.io - command can be stop|info|delete|restart')
.option('--ws', 'websocket mode')
.description('link with the pm2 monitoring dashboard')
.action(pm2._pre_interact.bind(pm2));
commander.command('unlink')
.description('linking action to keymetrics.io - command can be stop|info|delete|restart')
.description('unlink with the pm2 monitoring dashboard')
.action(function() {
pm2.unlink();
});
commander.command('unmonitor [name]')
.description('unmonitor target process')
.action(function(name) {
pm2.monitorState('unmonitor', name);
});
commander.command('monitor [name]')
.description('monitor target process')
.action(function(name) {
pm2.monitorState('monitor', name);
});
commander.command('unmonitor [name]')
.description('unmonitor target process')
.action(function(name) {
pm2.monitorState('unmonitor', name);
});
commander.command('open')
.description('open dashboard in browser')
.description('open the pm2 monitoring dashboard')
.action(function(name) {
pm2.openDashboard();
});
commander.command('register')
.description('create an account on keymetrics')
.description('register on pm2 monitoring')
.action(function(name) {
pm2.registerToKM();
});
commander.command('login')
.description('login to keymetrics and link current PM2')
.description('use login to link with the pm2 monitoring dashboard')
.action(function(name) {
pm2.loginToKM();
});
@ -612,6 +627,15 @@ commander.command('dump')
pm2.dump();
}));
//
// Delete dump file
//
commander.command('cleardump')
.description('Create empty dump file')
.action(failOnUnknown(function() {
pm2.clearDump();
}));
//
// Save processes to file
//
@ -645,7 +669,7 @@ commander.command('resurrect')
// Set pm2 to startup
//
commander.command('unstartup [platform]')
.description('disable and clear auto startup - [platform]=systemd,upstart,launchd,rcd')
.description('disable the pm2 startup hook')
.action(function(platform) {
pm2.uninstallStartup(platform, commander);
});
@ -654,7 +678,7 @@ commander.command('unstartup [platform]')
// Set pm2 to startup
//
commander.command('startup [platform]')
.description('setup script for pm2 at boot - [platform]=systemd,upstart,launchd,rcd')
.description('enable the pm2 startup hook')
.action(function(platform) {
pm2.startup(platform, commander);
});
@ -891,15 +915,6 @@ commander.command('backward <name>')
pm2.backward(pm2_name);
});
//
// Force PM2 to trigger garbage collection
//
commander.command('gc')
.description('force PM2 to trigger garbage collection')
.action(function() {
pm2.forceGc();
});
//
// Perform a deep update of PM2
//
@ -914,18 +929,27 @@ commander.command('deepUpdate')
//
commander.command('serve [path] [port]')
.alias('expose')
.option('--port [port]', 'specify port to listen to')
.description('serve a directory over http via port')
.action(function (path, port) {
pm2.serve(path, port, commander);
.action(function (path, port, cmd) {
pm2.serve(path, port || cmd.port, commander);
});
commander.command('examples')
.description('display pm2 usage examples')
.action(() => {
console.log(cst.PREFIX_MSG + chalk.grey('pm2 usage examples:\n'));
displayExamples();
process.exit(cst.SUCCESS_EXIT);
})
//
// Catch all
//
commander.command('*')
.action(function() {
console.log(cst.PREFIX_MSG + '\nCommand not found');
commander.outputHelp();
console.log(cst.PREFIX_MSG + 'Command not found\n');
displayUsage();
// Check if it does not forget to close fds from RPC
process.exit(cst.ERROR_EXIT);
});
@ -935,7 +959,7 @@ commander.command('*')
//
if (process.argv.length == 2) {
commander.parse(process.argv);
commander.outputHelp();
displayUsage();
// Check if it does not forget to close fds from RPC
process.exit(cst.ERROR_EXIT);
}

View File

@ -8,7 +8,6 @@ var debug = require('debug')('pm2:conf');
var p = require('path');
var util = require('util');
var chalk = require('chalk');
var semver = require('semver');
/**
* Get PM2 path structure
@ -28,7 +27,7 @@ var csts = {
TEMPLATE_FOLDER : p.join(__dirname, 'lib/templates'),
APP_CONF_DEFAULT_FILE : 'ecosystem.json',
APP_CONF_DEFAULT_FILE : 'ecosystem.config.js',
APP_CONF_TPL : 'ecosystem.tpl',
APP_CONF_TPL_SIMPLE : 'ecosystem-simple.tpl',
SAMPLE_CONF_FILE : 'sample-conf.js',
@ -55,7 +54,12 @@ var csts = {
LOW_MEMORY_ENVIRONMENT : process.env.PM2_OPTIMIZE_MEMORY || false,
KEYMETRICS_ROOT_URL : process.env.KEYMETRICS_NODE || 'root.keymetrics.io',
MACHINE_NAME : process.env.INSTANCE_NAME || process.env.MACHINE_NAME,
SECRET_KEY : process.env.KEYMETRICS_SECRET || process.env.PM2_SECRET_KEY || process.env.SECRET_KEY,
PUBLIC_KEY : process.env.KEYMETRICS_PUBLIC || process.env.PM2_PUBLIC_KEY || process.env.PUBLIC_KEY,
KEYMETRICS_ROOT_URL : process.env.KEYMETRICS_NODE || process.env.ROOT_URL || process.env.INFO_NODE || 'root.keymetrics.io',
KEYMETRICS_BANNER : '../lib/motd',
KEYMETRICS_UPDATE : '../lib/motd.update',
DEFAULT_MODULE_JSON : 'package.json',
@ -74,10 +78,7 @@ var csts = {
// Concurrent actions when doing start/restart/reload
CONCURRENT_ACTIONS : (function() {
var default_concurrent_actions = 1;
if (semver.satisfies(process.versions.node, '>= 4.0.0'))
default_concurrent_actions = 2;
var concurrent_actions = parseInt(process.env.PM2_CONCURRENT_ACTIONS) || default_concurrent_actions;
var concurrent_actions = parseInt(process.env.PM2_CONCURRENT_ACTIONS) || 2;
debug('Using %d parallelism (CONCURRENT_ACTIONS)', concurrent_actions);
return concurrent_actions;
})(),
@ -91,7 +92,7 @@ var csts = {
WORKER_INTERVAL : process.env.PM2_WORKER_INTERVAL || 30000,
KILL_TIMEOUT : process.env.PM2_KILL_TIMEOUT || 1600,
PM2_PROGRAMMATIC : typeof(process.env.pm_id) !== 'undefined' || process.env.PM2_PROGRAMMATIC,
PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DD HH:mm:ss'
PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'
};

View File

@ -2,7 +2,7 @@
/*
* Example of graceful exit
*
* $ pm2 gracefulReload all
* $ pm2 reload all
*/
process.on('message', function(msg) {

View File

@ -399,33 +399,6 @@ API.prototype.update = function(cb) {
return false;
};
/**
* Graceful Reload an application
*
* @param {String} process_name Application Name or All
* @param {Object} opts Options
* @param {Function} cb Callback
*/
API.prototype.gracefulReload = function(process_name, opts, cb) {
var that = this;
if (typeof(opts) == "function") {
cb = opts;
opts = {};
}
//Common.printOut(conf.PREFIX_MSG_WARNING + chalk.bold.yellow('Warning gracefulReload will be soon deprecated'));
//Common.printOut(conf.PREFIX_MSG_WARNING + chalk.bold.yellow('Use http://pm2.keymetrics.io/docs/usage/signals-clean-restart/ instead'));
if (Common.isConfigFile(process_name))
that._startJson(process_name, commander, 'softReloadProcessId');
else {
if (opts && !opts.updateEnv)
Common.printOut(IMMUTABLE_MSG);
that._operate('softReloadProcessId', process_name, opts, cb);
}
};
/**
* Reload an application
*

View File

@ -47,7 +47,7 @@ function fib(n) {
}
var axm = require('pmx');
var axm = require('@pm2/io');
axm.action('load:start', function(reply) {
fib(50000);

View File

@ -1,5 +1,5 @@
var axm = require('pmx');
var axm = require('@pm2/io');
axm.action('getEnv', function(reply) {
reply(process.env);

View File

@ -1,5 +1,5 @@
var axm = require('pmx');
var axm = require('@pm2/io');
axm.action('refresh:db', { comment : 'Refresh the database' }, function(reply) {
console.log('Refreshing');

View File

@ -1,5 +1,5 @@
var axm = require('pmx');
var axm = require('@pm2/io');
setInterval(function() {

View File

@ -1,7 +1,7 @@
var pmx = require('pmx').init({ http : true });
var probe = pmx.probe();
var io = require('@pm2/io').init({ http : true });
var probe = io.probe();
var http = require('http');

View File

@ -1,6 +1,6 @@
var axm = require('pmx');
var axm = require('@pm2/io');
var probe = axm.probe();
@ -17,7 +17,7 @@ http.createServer(function(req, res) {
setTimeout(function() {
res.end('transaction');
}, 1000);
}).listen(9010);
}).listen(10010);
setInterval(function() {
request(['/user', '/bla', '/user/lol/delete', '/POST/POST'][Math.floor((Math.random() * 4))]);

View File

@ -1,16 +1,15 @@
var pmx = require('pmx');
var io = require('@pm2/io');
var pm2 = require('../..');
var fs = require('fs');
var path = require('path');
var conf = pmx.initModule({
var conf = io.initModule({
comment : 'This module monitors PM2',
errors : true,
latency : false,
versioning : false,
show_module_meta : false,
module_type : 'database',
pid : pmx.getPID(path.join(process.env.HOME, '.pm2', 'pm2.pid')),
widget : {
theme : ['#111111', '#1B2228', '#807C7C', '#807C7C'],
@ -18,7 +17,7 @@ var conf = pmx.initModule({
}
});
var probe = pmx.probe();
var probe = io.probe();
var pm2_procs = 0;

View File

@ -1,6 +1,6 @@
var axm = require('pmx');
var axm = require('@pm2/io');
var probe = axm.probe();
@ -84,7 +84,7 @@ setInterval(function() {
}, 1500);
axm.catchAll();
//axm.catchAll();
axm.action('throw error', function(reply) {
setTimeout(function() {

View File

@ -1,21 +1,21 @@
var Probe = require('pmx').probe();
var Probe = require('@pm2/io').probe();
var counter = 0;
var metric = Probe.transpose({
name : 'data-flow',
data : function() {
return {
a : {
b : {
data : 'textflow',
array : [ 'yes', 'it', 'is' ]
}
}
}
}
});
// var metric = Probe.transpose({
// name : 'data-flow',
// data : function() {
// return {
// a : {
// b : {
// data : 'textflow',
// array : [ 'yes', 'it', 'is' ]
// }
// }
// }
// }
// });
setInterval(function() {
}, 100);

View File

@ -1,7 +1,7 @@
var pmx = require('pmx');
var io = require('@pm2/io');
pmx.scopedAction('simple test', function(data, emitter) {
io.scopedAction('simple test', function(data, emitter) {
var i = setInterval(function() {
emitter.send('output-stream');
}, 100);
@ -13,7 +13,7 @@ pmx.scopedAction('simple test', function(data, emitter) {
}, 3000);
});
pmx.scopedAction('throwing error', function(data, emitter) {
io.scopedAction('throwing error', function(data, emitter) {
var i = setInterval(function() {
emitter.send('output-stream');
}, 100);

View File

@ -1,7 +1,5 @@
var axm = require('pmx');
axm.catchAll();
var axm = require('@pm2/io');
setTimeout(function() {
console.log('log message from echo auto kill');

3208
lib/API.js

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
var Password = require('../Interactor/Password.js');
var Common = require('../Common.js');
var cst = require('../../constants.js');
var UX = require('./CliUx');
@ -7,7 +6,7 @@ var chalk = require('chalk');
var async = require('async');
var Configuration = require('../Configuration.js');
//@todo double check that imported methods works
var InteractorDaemonizer = require('../Interactor/InteractorDaemonizer');
var InteractorDaemonizer = require('@pm2/agent/src/InteractorClient');
module.exports = function(CLI) {
@ -63,36 +62,6 @@ module.exports = function(CLI) {
return false;
}
/**
* Specific when setting pm2 password
* Used for restricted remote actions
* Also alert Interactor that password has been set
*/
if (key.indexOf('pm2:passwd') > -1) {
value = Password.generate(value);
Configuration.set(key, value, function(err) {
if (err)
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
InteractorDaemonizer.launchRPC(that._conf, function(err) {
if (err) {
displayConf('pm2', function() {
return cb ? cb(null, {success:true}) : that.exitCli(cst.SUCCESS_EXIT)
});
return false;
}
InteractorDaemonizer.rpc.passwordSet(function() {
InteractorDaemonizer.disconnectRPC(function() {
displayConf('pm2', function() {
return cb ? cb(null, {success:true}) : that.exitCli(cst.SUCCESS_EXIT);
});
});
});
return false;
});
});
return false;
}
/**
* Set value
*/

View File

@ -4,7 +4,6 @@ var exec = require('child_process').exec;
var chalk = require('chalk');
var util = require('util');
var fmt = require('../tools/fmt.js');
var vizion = require('vizion');
var fs = require('fs');
var path = require('path');
var cst = require('../../constants.js');
@ -148,7 +147,7 @@ function handleExit(CLI, opts, mode) {
if (err) {
console.error(err);
}
vizion.analyze({folder : process.cwd()}, function recur_path(err, meta){
require('vizion').analyze({folder : process.cwd()}, function recur_path(err, meta){
if (!err && meta.revision) {
var commit_id = util.format('#%s(%s) %s',
meta.branch,

View File

@ -162,7 +162,7 @@ Dashboard.init = function() {
});
this.box4 = blessed.text({
content: ' left/right: switch boards | up/down/mouse: scroll | Ctrl-C: exit{|} {cyan-fg}{bold}To go further check out https://keymetrics.io/{/} ',
content: ' left/right: switch boards | up/down/mouse: scroll | Ctrl-C: exit{|} {cyan-fg}{bold}To go further check out https://pm2.io/{/} ',
left: '0%',
top: '95%',
width: '100%',

View File

@ -5,8 +5,6 @@
*/
var fs = require('fs');
var Deploy = require('pm2-deploy');
var cst = require('../../constants.js');
var Utility = require('../Utility.js');
var Common = require('../Common.js');
@ -104,7 +102,7 @@ module.exports = function(CLI) {
json_conf.deploy[env]['post-deploy'] = 'pm2 startOrRestart ' + file + ' --env ' + env;
}
Deploy.deployForEnv(json_conf.deploy, env, args, function(err, data) {
require('pm2-deploy').deployForEnv(json_conf.deploy, env, args, function(err, data) {
if (err) {
Common.printError('Deploy failed');
return cb ? cb(err) : that.exitCli(cst.ERROR_EXIT);

View File

@ -15,6 +15,7 @@ var fs = require('fs');
var fmt = require('../tools/fmt.js');
var moment = require('moment');
var pkg = require('../../package.json');
const semver = require('semver');
module.exports = function(CLI) {
@ -38,7 +39,6 @@ module.exports = function(CLI) {
*/
CLI.prototype.report = function() {
var that = this;
var semver = require('semver');
function reporting(cb) {
@ -108,7 +108,7 @@ module.exports = function(CLI) {
}
that.Client.executeRemote('getVersion', {}, function(err, data) {
if (semver.satisfies(data, '>= 2.6.0'))
if (semver.satisfies(semver.coerce(data), '>=2.6.0'))
reporting();
else {
Common.printError(cst.PREFIX_MSG_ERR + 'You need to update your Daemon, please type `$ pm2 update`');
@ -639,4 +639,27 @@ module.exports = function(CLI) {
launchMonitor();
};
CLI.prototype.inspect = function(app_name, cb) {
const that = this;
if(semver.satisfies(process.versions.node, '>= 8.0.0')) {
this.trigger(app_name, 'internal:inspect', function (err, res) {
if(res && res[0]) {
if (res[0].data.return === '') {
Common.printOut(`Inspect disabled on ${app_name}`);
} else {
Common.printOut(`Inspect enabled on ${app_name} => go to chrome : chrome://inspect !!!`);
}
} else {
Common.printOut(`Unable to activate inspect mode on ${app_name} !!!`);
}
that.exitCli(cst.SUCCESS_EXIT);
});
} else {
Common.printOut('Inspect is available for node version >=8.x !');
that.exitCli(cst.SUCCESS_EXIT);
}
};
};

View File

@ -6,7 +6,8 @@ var chalk = require('chalk');
var async = require('async');
var path = require('path');
var fs = require('fs');
var KMDaemon = require('../Interactor/InteractorDaemonizer');
var KMDaemon = require('@pm2/agent/src/InteractorClient');
var pkg = require('../../package.json')
module.exports = function(CLI) {
@ -45,7 +46,8 @@ module.exports = function(CLI) {
KMDaemon.launchAndInteract(that._conf, {
secret_key : secret_key || null,
public_key : public_key || null,
machine_name : machine_name || null
machine_name : machine_name || null,
pm2_version: pkg.version
}, function(err, dt) {
if (err) {
return cb ? cb(err) : that.exitCli(cst.ERROR_EXIT);
@ -74,20 +76,20 @@ module.exports = function(CLI) {
//
// Interact
//
CLI.prototype._pre_interact = function(cmd, public_key, machine, info_node) {
CLI.prototype._pre_interact = function(cmd, public_key, machine, opts) {
var that = this;
if (cmd == 'stop' || cmd == 'kill') {
console.log(chalk.cyan('[Keymetrics.io]') + ' Stopping agent...');
console.log(chalk.cyan('[PM2 agent]') + ' Stopping agent...');
that.killInteract(function() {
console.log(chalk.cyan('[Keymetrics.io]') + ' Stopped');
console.log(chalk.cyan('[PM2 agent]') + ' Stopped');
return process.exit(cst.SUCCESS_EXIT);
});
return false;
}
if (cmd == 'info') {
console.log(chalk.cyan('[Keymetrics.io]') + ' Getting agent information...');
console.log(chalk.cyan('[PM2 agent]') + ' Getting agent information...');
that.interactInfos(function(err, infos) {
if (err) {
console.error(err.message);
@ -104,10 +106,10 @@ module.exports = function(CLI) {
try {
fs.unlinkSync(cst.INTERACTION_CONF);
} catch(e) {
console.log(chalk.cyan('[Keymetrics.io]') + ' No interaction config file found');
console.log(chalk.cyan('[PM2 agent]') + ' No interaction config file found');
return process.exit(cst.SUCCESS_EXIT);
}
console.log(chalk.cyan('[Keymetrics.io]') + ' Agent interaction ended');
console.log(chalk.cyan('[PM2 agent]') + ' Agent interaction ended');
return process.exit(cst.SUCCESS_EXIT);
});
return false;
@ -118,7 +120,8 @@ module.exports = function(CLI) {
public_key : null,
secret_key : null,
machine_name : null,
info_node : null
info_node : null,
pm2_version: pkg.version
}, function(err, dt) {
if (err) {
Common.printError(err);
@ -129,7 +132,7 @@ module.exports = function(CLI) {
}
if (cmd && !public_key) {
console.error(chalk.cyan('[Keymetrics.io]') + ' Command [%s] unknown or missing public key', cmd);
console.error(chalk.cyan('[PM2 agent]') + ' Command [%s] unknown or missing public key', cmd);
return process.exit(cst.ERROR_EXIT);
}
@ -143,9 +146,23 @@ module.exports = function(CLI) {
public_key : public_key,
secret_key : cmd,
machine_name : machine,
info_node : info_node.infoNode || null
info_node : opts.infoNode || null,
pm2_version: pkg.version
}
if (opts.ws === true && infos) {
infos.agent_transport_axon = false
infos.agent_transport_websocket = true
process.env.AGENT_TRANSPORT_AXON = false
process.env.AGENT_TRANSPORT_WEBSOCKET = true
}
else if (infos) {
infos.agent_transport_axon = true
infos.agent_transport_websocket = false
process.env.AGENT_TRANSPORT_AXON = true
process.env.AGENT_TRANSPORT_WEBSOCKET = false
}
KMDaemon.launchAndInteract(that._conf, infos, function(err, dt) {
if (err)
return that.exitCli(cst.ERROR_EXIT);

View File

@ -1,248 +0,0 @@
var cst = require('../../../constants.js');
var Common = require('../../Common.js');
var UX = require('../CliUx');
var chalk = require('chalk');
var async = require('async');
var path = require('path');
var fs = require('fs');
var KMDaemon = require('../../Interactor/InteractorDaemonizer');
var KM = require('./kmapi.js');
var Table = require('cli-table-redemption');
var open = require('../../tools/open.js');
var promptly = require('promptly');
module.exports = function(CLI) {
CLI.prototype.openDashboard = function() {
var that = this;
KMDaemon.getInteractInfo(this._conf, function(err, data) {
if (err) {
Common.printError(chalk.bold.white('Agent if offline, type `$ pm2 register` to log in'));
return that.exitCli(cst.ERROR_EXIT);
}
Common.printOut(chalk.bold('Opening Dashboard in Browser...'));
open('https://app.keymetrics.io/#/r/' + data.public_key);
setTimeout(function() {
that.exitCli();
}, 200);
});
};
CLI.prototype.loginToKM = function() {
printMotd();
return loginPrompt();
};
CLI.prototype.registerToKM = function() {
printMotd();
promptly.confirm(chalk.bold('Do you have a Keymetrics.io account? (y/n)'), function (err, answer) {
if (answer == true) {
return loginPrompt();
}
registerPrompt();
});
};
/**
* Monitor Selectively Processes (auto filter in interaction)
* @param String state 'monitor' or 'unmonitor'
* @param String target <pm_id|name|all>
* @param Function cb callback
*/
CLI.prototype.monitorState = function(state, target, cb) {
var that = this;
if (process.env.NODE_ENV !== 'test') {
try {
fs.statSync(this._conf.INTERACTION_CONF);
} catch(e) {
printMotd();
return registerPrompt();
}
}
if (!target) {
Common.printError(cst.PREFIX_MSG_ERR + 'Please specify an <app_name|pm_id>');
return cb ? cb(new Error('argument missing')) : that.exitCli(cst.ERROR_EXIT);
}
function monitor (pm_id, cb) {
// State can be monitor or unmonitor
that.Client.executeRemote(state, pm_id, cb);
}
if (target === 'all') {
that.Client.getAllProcessId(function (err, procs) {
if (err) {
Common.printError(err);
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
}
async.forEachLimit(procs, 1, monitor, function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
});
} else if (!Number.isInteger(parseInt(target))) {
this.Client.getProcessIdByName(target, true, function (err, procs) {
if (err) {
Common.printError(err);
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
}
async.forEachLimit(procs, 1, monitor, function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
});
} else {
monitor(parseInt(target), function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
}
};
/**
* Private Functions
*/
function printMotd() {
var dt = fs.readFileSync(path.join(__dirname, 'motd'));
console.log(dt.toString());
}
function validateEmail(email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(email) == false)
throw new Error('Not an email');
return email;
}
function validateUsername(value) {
if (value.length < 6) {
throw new Error('Min length of 6');
}
return value;
};
function linkOpenExit(target_bucket) {
KMDaemon.launchAndInteract(cst, {
public_key : target_bucket.public_id,
secret_key : target_bucket.secret_id
}, function(err, dt) {
open('https://app.keymetrics.io/#/r/' + target_bucket.public_id);
setTimeout(function() {
process.exit(cst.SUCCESS_EXIT);
}, 100);
});
}
/**
* Login on Keymetrics
* Link to the only bucket or list bucket for selection
* Open Browser
*/
function loginPrompt(cb) {
console.log(chalk.bold('Log in to Keymetrics'));
(function retry() {
promptly.prompt('Username or Email: ', function(err, username) {
promptly.password('Password: ', { replace : '*' }, function(err, password) {
KM.loginAndGetAccessToken({ username : username, password: password }, function(err) {
if (err) {
console.error(chalk.red.bold(err) + '\n');
return retry();
}
KM.getBuckets(function(err, buckets) {
if (err) {
console.error(chalk.red.bold(err) + '\n');
return retry();
}
if (buckets.length > 1) {
console.log(chalk.bold('Bucket list'));
var table = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
head : ['Bucket name', 'Plan type']
});
buckets.forEach(function(bucket) {
table.push([bucket.name, bucket.credits.offer_type]);
});
console.log(table.toString());
(function retryInsertion() {
promptly.prompt('Type the bucket you want to link to: ', function(err, bucket_name) {
var target_bucket = null;
buckets.some(function(bucket) {
if (bucket.name == bucket_name) {
target_bucket = bucket;
return true;
}
});
if (target_bucket == null)
return retryInsertion();
linkOpenExit(target_bucket);
});
})();
}
else {
var target_bucket = buckets[0];
console.log('Connecting local PM2 to Keymetrics Bucket [%s]', target_bucket.name);
KMDaemon.launchAndInteract(cst, {
public_key : target_bucket.public_id,
secret_key : target_bucket.secret_id
}, function(err, dt) {
linkOpenExit(target_bucket);
});
}
});
});
});
})
})()
}
/**
* Register on Keymetrics
* Create Bucket
* Auto Link local PM2 to new Bucket
* Open Browser for access to monitoring dashboard
*/
function registerPrompt() {
console.log(chalk.bold('Now registering to Keymetrics'));
promptly.prompt('Username: ', {
validator : validateUsername,
retry : true
}, function(err, username) {
promptly.prompt('Email: ', {
validator : validateEmail,
retry : true
}, function(err, email) {
promptly.password('Password: ', { replace : '*' }, function(err, password) {
process.stdout.write(chalk.bold('Creating account on Keymetrics..'));
var inter = setInterval(function() {
process.stdout.write('.');
}, 300);
KM.fullCreationFlow({
email : email,
password : password,
username : username
}, function(err, target_bucket) {
clearInterval(inter);
if (err) {
console.error('\n' + chalk.red.bold(err) + '\n');
return registerPrompt();
}
linkOpenExit(target_bucket);
});
});
});
})
}
};

View File

@ -1,162 +0,0 @@
var querystring = require('querystring');
var https = require('https');
var fs = require('fs');
var needle = require('needle');
var url = require('url');
var cst = require('../../../constants.js');
var KM = function() {
this.BASE_URI = 'https://app.keymetrics.io';
this.CLIENT_ID = '938758711';
this.CB_URI = 'https://app.keymetrics.io';
this.ACCESS_TOKEN_FILE = cst.KM_ACCESS_TOKEN;
this.access_token = null;
}
/**
* @param user_info.username
* @param user_info.password
* @return promise
*/
KM.prototype.loginAndGetAccessToken = function (user_info, cb) {
var that = this;
var URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' +
that.CLIENT_ID + '&redirect_uri=' + that.CB_URI;
needle.get(that.BASE_URI + URL_AUTH, function(err, res) {
if (err) return cb(err);
var cookie = res.cookies;
needle.post(that.BASE_URI + '/api/oauth/login', user_info, {
cookies : cookie
}, function(err, resp, body) {
if (err) return cb(err);
if (body.indexOf('/api/oauth/login') > -1) return cb('Wrong credentials');
var location = resp.headers.location;
var redirect = that.BASE_URI + location;
needle.get(redirect, {
cookies : cookie
}, function(err, res) {
if (err) return cb(err);
var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token;
needle.post(that.BASE_URI + '/api/oauth/token', {
client_id : that.CLIENT_ID,
grant_type : 'refresh_token',
refresh_token : refresh_token,
scope : 'all'
}, function(err, res, body) {
if (err) return cb(err);
that.access_token = body.access_token;
return cb(null, body.access_token);
})
});
});
});
}
KM.prototype.getLocalAccessToken = function(cb) {
var that = this;
fs.readFile(that.ACCESS_TOKEN_FILE, function(e, content) {
if (e) return cb(e);
cb(null, content.toString());
});
};
KM.prototype.saveLocalAccessToken = function(access_token, cb) {
var that = this;
fs.writeFile(that.ACCESS_TOKEN_FILE, access_token, function(e, content) {
if (e) return cb(e);
cb();
});
};
KM.prototype.getBuckets = function(cb) {
var that = this;
needle.get(that.BASE_URI + '/api/bucket', {
headers : {
'Authorization' : 'Bearer ' + that.access_token
},
json : true
}, function(err, res, body) {
if (err) return cb(err);
return cb(null, body);
});
}
/**
* @param user_info.username
* @param user_info.password
* @param user_info.email
* @return promise
*/
KM.prototype.register = function(user_info, cb) {
var that = this;
needle.post(that.BASE_URI + '/api/oauth/register', user_info, {
json: true,
headers: {
'X-Register-Provider': 'pm2-register'
}
}, function (err, res, body) {
if (err) return cb(err);
if (body.email && body.email.message) return cb(body.email.message);
if (body.username && body.username.message) return cb(body.username.message);
cb(null, {
token : body.access_token.token
})
});
};
KM.prototype.defaultNode = function(cb) {
var that = this;
needle.get(that.BASE_URI + '/api/node/default', function(err, res, body) {
if (err) return cb(err);
cb(null, url.parse(body.endpoints.web).protocol + '//' + url.parse(body.endpoints.web).hostname);
});
}
KM.prototype.createBucket = function(default_node, bucket_name, cb) {
var that = this;
needle.post(default_node + '/api/bucket/create_classic', {
name : bucket_name
}, {
json : true,
headers : {
'Authorization' : 'Bearer ' + that.access_token
}
}, function(err, res, body) {
if (err) return cb(err);
cb(null, body);
});
}
KM.prototype.fullCreationFlow = function(user_info, cb) {
var that = this;
this.register(user_info, function(err, dt) {
if (err) return cb(err);
that.access_token = dt.token;
that.defaultNode(function(err, default_node) {
if (err) return cb(err);
that.createBucket(default_node, 'Node Monitoring', function(err, packet) {
if (err) return cb(err);
return cb(null, {
secret_id : packet.bucket.secret_id,
public_id : packet.bucket.public_id
});
});
})
});
}
module.exports = new KM;

View File

@ -1,8 +0,0 @@
__ __ __ _
/ //_/__ __ ______ ___ ___ / /______(_)_________
/ ,< / _ \/ / / / __ `__ \/ _ \/ __/ ___/ / ___/ ___/
/ /| / __/ /_/ / / / / / / __/ /_/ / / / /__(__ )
/_/ |_\___/\__, /_/ /_/ /_/\___/\__/_/ /_/\___/____/
/____/
Harden your Node.js application, today

View File

@ -3,9 +3,9 @@
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var shelljs = require('shelljs');
var path = require('path');
var fs = require('fs');
var os = require('os');
var async = require('async');
var p = path;
var readline = require('readline');
@ -17,7 +17,6 @@ var Common = require('../../Common');
var Utility = require('../../Utility.js');
var ModularizerV1 = require('./Modularizerv1.js');
var Modularizer = module.exports = {};
var mkdirp = require('mkdirp');
var MODULE_CONF_PREFIX = 'module-db-v2';
@ -47,7 +46,7 @@ var KNOWN_MODULES = {
* - Generate sample module via pm2 module:generate <module_name>
*/
Modularizer.install = function (CLI, moduleName, opts, cb) {
// if user want to install module from ecosystem.json file
// if user want to install module from ecosystem.config.js file
// it can also be a custom json file's name
if (!moduleName || moduleName.length === 0 || moduleName.indexOf('.json') > 0) {
var file = moduleName || cst.APP_CONF_DEFAULT_FILE;
@ -184,8 +183,8 @@ Modularizer.installModule = function(CLI, module_name, opts, cb) {
var canonic_module_name = Utility.getCanonicModuleName(module_name);
var install_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name);
mkdirp(install_path, function() {
process.chdir(process.env.HOME);
require('mkdirp')(install_path, function() {
process.chdir(os.homedir());
var install_instance = spawn(cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error', '--prefix', install_path ], {
stdio : 'inherit',
@ -404,14 +403,14 @@ function uninstallModule(CLI, opts, cb) {
if (module_name != '.') {
console.log(proc_path);
shelljs.rm('-r', proc_path);
require('shelljs').rm('-r', proc_path);
}
return cb(err);
}
if (module_name != '.') {
shelljs.rm('-r', proc_path);
require('shelljs').rm('-r', proc_path);
}
return cb(null, data);
@ -435,9 +434,9 @@ var Rollback = {
CLI.deleteModule(canonic_module_name, function() {
// Delete failing module
shelljs.rm('-r', module_path);
require('shelljs').rm('-r', module_path);
// Restore working version
shelljs.cp('-r', backup_path, module_path);
require('shelljs').cp('-r', backup_path, cst.DEFAULT_MODULE_PATH);
var proc_path = path.join(module_path, 'node_modules', canonic_module_name);
var package_json_path = path.join(proc_path, 'package.json');
@ -455,7 +454,7 @@ var Rollback = {
var tmpdir = require('os').tmpdir();
var canonic_module_name = Utility.getCanonicModuleName(module_name);
var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name);
shelljs.cp('-r', module_path, tmpdir);
require('shelljs').cp('-r', module_path, tmpdir);
}
}
@ -520,13 +519,13 @@ Modularizer.publish = function(cb) {
package_json.name,
package_json.version);
shelljs.exec('npm publish', function(code) {
require('shelljs').exec('npm publish', function(code) {
Common.printOut(cst.PREFIX_MSG_MOD + 'Module - %s@%s successfully published',
package_json.name,
package_json.version);
Common.printOut(cst.PREFIX_MSG_MOD + 'Pushing module on Git');
shelljs.exec('git add . ; git commit -m "' + package_json.version + '"; git push origin master', function(code) {
require('shelljs').exec('git add . ; git commit -m "' + package_json.version + '"; git push origin master', function(code) {
Common.printOut(cst.PREFIX_MSG_MOD + 'Installable with pm2 install %s', package_json.name);
return cb(null, package_json);
@ -549,11 +548,11 @@ Modularizer.generateSample = function(app_name, cb) {
var cmd3 = 'cd ' + module_name + ' ; npm install';
Common.printOut(cst.PREFIX_MSG_MOD + 'Getting sample app');
shelljs.exec(cmd1, function(err) {
require('shelljs').exec(cmd1, function(err) {
if (err) Common.printError(cst.PREFIX_MSG_MOD_ERR + err.message);
shelljs.exec(cmd2, function(err) {
require('shelljs').exec(cmd2, function(err) {
console.log('');
shelljs.exec(cmd3, function(err) {
require('shelljs').exec(cmd3, function(err) {
console.log('');
Common.printOut(cst.PREFIX_MSG_MOD + 'Module sample created in folder: ', path.join(process.cwd(), module_name));
console.log('');

View File

@ -3,7 +3,6 @@
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var shelljs = require('shelljs');
var path = require('path');
var fs = require('fs');
var async = require('async');

View File

@ -11,7 +11,6 @@ var UX = require('../CliUx');
var chalk = require('chalk');
var async = require('async');
var shelljs = require('shelljs');
var path = require('path');
var fs = require('fs');
var p = path;

View File

@ -45,7 +45,7 @@ Monit.reset = function(msg) {
this.multi.charm.reset();
this.multi.write('\x1B[32m⌬ PM2 \x1B[39mmonitoring\x1B[96m (To go further check out https://app.keymetrics.io) \x1B[39m\n\n');
this.multi.write('\x1B[32m⌬ PM2 \x1B[39mmonitoring\x1B[96m (To go further check out https://app.pm2.io) \x1B[39m\n\n');
if(msg) {
this.multi.write(msg);
@ -244,4 +244,4 @@ Monit.updateBars = function(proc) {
return this;
}
module.exports = Monit;
module.exports = Monit;

285
lib/API/PM2/CliAuth.js Normal file
View File

@ -0,0 +1,285 @@
'use strict'
const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
const http = require('http')
const fs = require('fs')
const url = require('url')
const exec = require('child_process').exec
const async = require('async')
const path = require('path')
const os = require('os')
const needle = require('needle');
const chalk = require('chalk')
const cst = require('../../../constants.js');
module.exports = class CustomStrategy extends AuthStrategy {
// the client will try to call this but we handle this part ourselves
retrieveTokens (km, cb) {
this.authenticated = false
this.callback = cb
this.km = km
this.BASE_URI = 'https://app.keymetrics.io';
}
// so the cli know if we need to tell user to login/register
isAuthenticated () {
return new Promise((resolve, reject) => {
if (this.authenticated) return resolve(true)
let tokensPath = cst.PM2_IO_ACCESS_TOKEN
fs.readFile(tokensPath, (err, tokens) => {
if (err && err.code === 'ENOENT') return resolve(false)
if (err) return reject(err)
// verify that the token is valid
try {
tokens = JSON.parse(tokens || '{}')
} catch (err) {
fs.unlinkSync(tokensPath)
return resolve(false)
}
// if the refresh tokens is here, the user could be automatically authenticated
return resolve(typeof tokens.refresh_token === 'string')
})
})
}
verifyToken (refresh) {
return this.km.auth.retrieveToken({
client_id: this.client_id,
refresh_token: refresh
})
}
// called when we are sure the user asked to be logged in
_retrieveTokens (optionalCallback) {
const km = this.km
const cb = this.callback
async.tryEach([
// try to find the token via the environement
(next) => {
if (!process.env.KM_TOKEN) {
return next(new Error('No token in env'))
}
this.verifyToken(process.env.KM_TOKEN)
.then((res) => {
return next(null, res.data)
}).catch(next)
},
// try to find it in the file system
(next) => {
return next(new Error('nope'))
fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
if (err) return next(err)
// verify that the token is valid
tokens = JSON.parse(tokens || '{}')
if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
return next(null, tokens)
}
this.verifyToken(tokens.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
},
// otherwise make the whole flow
(next) => {
return this.loginViaCLI((data) => {
// verify that the token is valid
this.verifyToken(data.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
}
], (err, result) => {
// if present run the optional callback
if (typeof optionalCallback === 'function') {
optionalCallback(err, result)
}
if (result.refresh_token) {
this.authenticated = true
let file = cst.PM2_IO_ACCESS_TOKEN
fs.writeFile(file, JSON.stringify(result), () => {
return cb(err, result)
})
} else {
return cb(err, result)
}
})
}
loginViaCLI (cb) {
var promptly = require('promptly');
let retry = () => {
promptly.prompt('Username or Email: ', (err, username) => {
if (err) return retry();
promptly.password('Password: ', { replace : '*' }, (err, password) => {
if (err) return retry();
this._loginUser({
username: username,
password: password
}, (err, data) => {
if (err) return retry()
cb(data)
})
})
})
}
retry()
}
_loginUser (user_info, cb) {
const querystring = require('querystring');
const AUTH_URI = 'https://id.keymetrics.io'
const URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' +
this.client_id + '&redirect_uri=https://app.keymetrics.io';
console.log(chalk.bold('[-] Logging to pm2.io'))
needle.get(AUTH_URI + URL_AUTH, (err, res) => {
if (err) return cb(err);
var cookie = res.cookies;
needle.post(AUTH_URI + '/api/oauth/login', user_info, {
cookies : cookie
}, (err, resp, body) => {
if (err) return cb(err);
if (resp.statusCode != 200) return cb('Wrong credentials');
var location = resp.headers['x-redirect'];
var redirect = AUTH_URI + location;
needle.get(redirect, {
cookies : cookie
}, (err, res) => {
if (err) return cb(err);
var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token;
needle.post(AUTH_URI + '/api/oauth/token', {
client_id : this.client_id,
grant_type : 'refresh_token',
refresh_token : refresh_token,
scope : 'all'
}, (err, res, body) => {
if (err) return cb(err);
console.log(chalk.bold.green('[+] Logged in!'))
return cb(null, body);
})
});
});
});
}
registerViaCLI (cb) {
var promptly = require('promptly');
console.log(chalk.bold('[-] Registering to pm2.io'));
var retry = () => {
promptly.prompt('Username: ', {
validator : this._validateUsername,
retry : true
}, (err, username) => {
promptly.prompt('Email: ', {
validator : this._validateEmail,
retry : true
},(err, email) => {
promptly.password('Password: ', { replace : '*' }, (err, password) => {
process.stdout.write('Creating account on pm2.io...');
var inter = setInterval(function() {
process.stdout.write('.');
}, 200);
this._registerUser({
email : email,
password : password,
username : username
}, (err, data) => {
clearInterval(inter)
if (err) {
console.error()
console.error(chalk.bold.red(err));
return retry()
}
console.log(chalk.green.bold('\n[+] Account created!'))
this._loginUser({
username: username,
password: password
}, (err, data) => {
this.callback(err, data)
return cb(err, data)
})
})
});
});
})
}
retry();
}
/**
* Register function
* @param user_info.username
* @param user_info.password
* @param user_info.email
*/
_registerUser (user_info, cb) {
needle.post(this.BASE_URI + '/api/oauth/register', user_info, {
json: true,
headers: {
'X-Register-Provider': 'pm2-register'
}
}, function (err, res, body) {
if (err) return cb(err);
if (body.email && body.email.message) return cb(body.email.message);
if (body.username && body.username.message) return cb(body.username.message);
if (!body.access_token) return cb(body.msg)
cb(null, {
token : body.refresh_token.token
})
});
}
_validateEmail (email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(email) == false)
throw new Error('Not an email');
return email;
}
_validateUsername (value) {
if (value.length < 6) {
throw new Error('Min length of 6');
}
return value;
};
deleteTokens (km) {
return new Promise((resolve, reject) => {
// revoke the refreshToken
km.auth.revoke()
.then(res => {
// remove the token from the filesystem
let file = cst.PM2_IO_ACCESS_TOKEN
fs.unlinkSync(file)
return resolve(res)
}).catch(reject)
})
}
}

245
lib/API/PM2/PM2IO.js Normal file
View File

@ -0,0 +1,245 @@
'use strict'
var cst = require('../../../constants.js');
var Common = require('../../Common.js');
var KMDaemon = require('@pm2/agent/src/InteractorClient');
const chalk = require('chalk');
const async = require('async');
const path = require('path');
const fs = require('fs');
const Table = require('cli-table-redemption');
const open = require('../../tools/open.js');
const pkg = require('../../../package.json')
const IOAPI = require('@pm2/js-api')
// const CustomStrategy = require('./custom_auth')
// const strategy = new CustomStrategy({
// client_id: '7412235273'
// })
const CLIAuth = require('./CliAuth')
const CLIAuthStrategy = new CLIAuth({
client_id: '938758711'
})
const io = new IOAPI().use(CLIAuthStrategy)
module.exports = function(CLI) {
CLI.prototype.openDashboard = function() {
KMDaemon.getInteractInfo(this._conf, (err, data) => {
if (err) {
Common.printError(chalk.bold.white('Agent if offline, type `$ pm2 register` to log in'));
return this.exitCli(cst.ERROR_EXIT);
}
Common.printOut(chalk.bold('Opening Dashboard in Browser...'));
open('https://app.pm2.io/#/r/' + data.public_key);
setTimeout(_ => {
this.exitCli();
}, 200);
});
};
CLI.prototype.loginToKM = function() {
var promptly = require('promptly')
printMotd();
return CLIAuthStrategy._retrieveTokens((err, tokens) => {
if (err) {
console.error(`Oups, a error happened : ${err}`)
process.exit(1)
}
// query both the user and all bucket
Promise.all([ io.user.retrieve(), io.bucket.retrieveAll() ])
.then(results => {
let user = results[0].data
let buckets = results[1].data
if (buckets.length > 1) {
var table = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
head : ['Bucket name', 'Plan type']
});
buckets.forEach(function(bucket) {
table.push([bucket.name, bucket.credits.offer_type]);
});
console.log(table.toString());
(function retryInsertion() {
promptly.prompt('Type the bucket you want to link to: ', function(err, bucket_name) {
var target_bucket = null;
buckets.some(function(bucket) {
if (bucket.name == bucket_name) {
target_bucket = bucket;
return true;
}
});
if (target_bucket == null)
return retryInsertion();
linkOpenExit(target_bucket);
});
})();
}
else {
var target_bucket = buckets[0];
linkOpenExit(target_bucket)
}
}).catch(err => {
console.error(chalk.bold.red(`Oups, a error happened : ${err}`))
return process.exit(1)
})
})
};
CLI.prototype.registerToKM = function() {
var promptly = require('promptly');
promptly.confirm(chalk.bold('Do you have a pm2.io account? (y/n)'), (err, answer) => {
if (answer == true) {
return this.loginToKM();
}
CLIAuthStrategy.registerViaCLI((err, data) => {
console.log('[-] Creating Bucket...')
io.bucket.create({
name: 'Node.JS Monitoring'
}).then(res => {
const bucket = res.data.bucket
console.log(chalk.bold.green('[+] Bucket created!'))
linkOpenExit(bucket)
})
})
});
}
CLI.prototype.logoutToKM = function () {
CLIAuthStrategy._retrieveTokens(_ => {
io.auth.logout()
.then(res => {
console.log(`- Logout successful`)
return process.exit(0)
}).catch(err => {
console.error(`Oups, a error happened : ${err.message}`)
return process.exit(1)
})
})
}
CLI.prototype.connectToPM2IO = function() {
io.bucket.create({
name: 'Node.JS Monitoring'
}).then(res => {
const bucket = res.data.bucket
console.log(`Succesfully created a bucket !`)
console.log(`To start using it, you should push data with :
pm2 link ${bucket.secret_id} ${bucket.public_id}
`)
console.log(`You can also access our dedicated UI by going here :
https://app.pm2.io/#/r/${bucket.public_id}
`)
KMDaemon.launchAndInteract(cst, {
public_key : bucket.public_id,
secret_key : bucket.secret_id
}, function(err, dt) {
open(`https://app.pm2.io/#/r/${bucket.public_id}`);
setTimeout(_ => {
return process.exit(0)
}, 200)
});
})
}
/**
* Monitor Selectively Processes (auto filter in interaction)
* @param String state 'monitor' or 'unmonitor'
* @param String target <pm_id|name|all>
* @param Function cb callback
*/
CLI.prototype.monitorState = function(state, target, cb) {
var that = this;
if (process.env.NODE_ENV !== 'test') {
try {
fs.statSync(this._conf.INTERACTION_CONF);
} catch(e) {
printMotd();
return this.registerToKM();
}
}
if (!target) {
Common.printError(cst.PREFIX_MSG_ERR + 'Please specify an <app_name|pm_id>');
return cb ? cb(new Error('argument missing')) : that.exitCli(cst.ERROR_EXIT);
}
function monitor (pm_id, cb) {
// State can be monitor or unmonitor
that.Client.executeRemote(state, pm_id, cb);
}
if (target === 'all') {
that.Client.getAllProcessId(function (err, procs) {
if (err) {
Common.printError(err);
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
}
async.forEachLimit(procs, 1, monitor, function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
});
} else if (!Number.isInteger(parseInt(target))) {
this.Client.getProcessIdByName(target, true, function (err, procs) {
if (err) {
Common.printError(err);
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
}
async.forEachLimit(procs, 1, monitor, function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
});
} else {
monitor(parseInt(target), function (err, res) {
return typeof cb === 'function' ? cb(err, res) : that.speedList();
});
}
};
function linkOpenExit(target_bucket) {
console.log('[-] Linking local PM2 to newly created bucket...')
KMDaemon.launchAndInteract(cst, {
public_key : target_bucket.public_id,
secret_key : target_bucket.secret_id,
pm2_version: pkg.version
}, function(err, dt) {
console.log(chalk.bold.green('[+] Local PM2 Connected!'))
console.log('[-] Opening Monitoring Interface in Browser...')
setTimeout(function() {
open('https://app.pm2.io/#/r/' + target_bucket.public_id);
console.log(chalk.bold.green('[+] Opened! Exiting now.'))
setTimeout(function() {
process.exit(cst.SUCCESS_EXIT);
}, 100);
}, 1000)
});
}
/**
* Private Functions
*/
function printMotd() {
var dt = fs.readFileSync(path.join(__dirname, 'motd'));
console.log(dt.toString());
}
};

192
lib/API/PM2/WebAuth.js Normal file
View File

@ -0,0 +1,192 @@
'use strict'
const cst = require('../../../constants.js');
const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
const http = require('http')
const fs = require('fs')
const url = require('url')
const exec = require('child_process').exec
const async = require('async')
const path = require('path')
const os = require('os')
const needle = require('needle');
module.exports = class CustomStrategy extends AuthStrategy {
// the client will try to call this but we handle this part ourselves
retrieveTokens (km, cb) {
this.authenticated = false
this.callback = cb
this.km = km
}
// so the cli know if we need to tell user to login/register
isAuthenticated () {
return new Promise((resolve, reject) => {
if (this.authenticated) return resolve(true)
let tokensPath = cst.PM2_IO_ACCESS_TOKEN
fs.readFile(tokensPath, (err, tokens) => {
if (err && err.code === 'ENOENT') return resolve(false)
if (err) return reject(err)
// verify that the token is valid
try {
tokens = JSON.parse(tokens || '{}')
} catch (err) {
fs.unlinkSync(tokensPath)
return resolve(false)
}
// if the refresh tokens is here, the user could be automatically authenticated
return resolve(typeof tokens.refresh_token === 'string')
})
})
}
// called when we are sure the user asked to be logged in
_retrieveTokens (optionalCallback) {
const km = this.km
const cb = this.callback
let verifyToken = (refresh) => {
return km.auth.retrieveToken({
client_id: this.client_id,
refresh_token: refresh
})
}
async.tryEach([
// try to find the token via the environement
(next) => {
if (!process.env.KM_TOKEN) {
return next(new Error('No token in env'))
}
verifyToken(process.env.KM_TOKEN)
.then((res) => {
return next(null, res.data)
}).catch(next)
},
// try to find it in the file system
(next) => {
return next(new Error('nope'))
fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
if (err) return next(err)
// verify that the token is valid
tokens = JSON.parse(tokens || '{}')
if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
return next(null, tokens)
}
verifyToken(tokens.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
},
// otherwise make the whole flow
(next) => {
return this.loginViaWeb((data) => {
// verify that the token is valid
verifyToken(data.access_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
}
], (err, result) => {
// if present run the optional callback
if (typeof optionalCallback === 'function') {
optionalCallback(err, result)
}
if (result.refresh_token) {
this.authenticated = true
let file = cst.PM2_IO_ACCESS_TOKEN
fs.writeFile(file, JSON.stringify(result), () => {
return cb(err, result)
})
} else {
return cb(err, result)
}
})
}
loginViaWeb (cb) {
let shutdown = false
let server = http.createServer((req, res) => {
// only handle one request
if (shutdown === true) return res.end()
shutdown = true
let query = url.parse(req.url, true).query
res.write(`
<head>
<script>
setTimeout(function () {
window.location = 'https://pm2.io/doc/en/plus/quick-start/'
}, 5000)
</script>
</head>
<body>
<h2 style="text-align: center">
You can go back to your terminal now :)
</h2>
</body>`)
res.end()
server.close()
return cb(query)
})
server.listen(43532, () => {
this.open(`${this.oauth_endpoint}${this.oauth_query}`)
})
}
deleteTokens (km) {
return new Promise((resolve, reject) => {
// revoke the refreshToken
km.auth.revoke()
.then(res => {
// remove the token from the filesystem
let file = cst.PM2_IO_ACCESS_TOKEN
fs.unlinkSync(file)
return resolve(res)
}).catch(reject)
})
}
open (target, appName, callback) {
let opener
const escape = function (s) {
return s.replace(/"/g, '\\"')
}
if (typeof (appName) === 'function') {
callback = appName
appName = null
}
switch (process.platform) {
case 'darwin': {
opener = appName ? `open -a "${escape(appName)}"` : `open`
break
}
case 'win32': {
opener = appName ? `start "" ${escape(appName)}"` : `start ""`
break
}
default: {
opener = appName ? escape(appName) : `xdg-open`
break
}
}
if (process.env.SUDO_USER) {
opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener
}
return exec(`${opener} "${escape(target)}"`, callback)
}
}

13
lib/API/PM2/motd Normal file
View File

@ -0,0 +1,13 @@
88888888ba 88b d88 ad888888b, 88 ,ad8888ba,
88 "8b 888b d888 d8" "88 88 d8"' `"8b
88 ,8P 88`8b d8'88 a8P 88 d8' `8b
88aaaaaa8P' 88 `8b d8' 88 ,d8P" 88 88 88
88""""""' 88 `8b d8' 88 a8P" 88 88 88
88 88 `8b d8' 88 a8P' 88 Y8, ,8P
88 88 `888' 88 d8" 888 88 Y8a. .a8P
88 88 `8' 88 88888888888 888 88 `"Y8888Y"'
https://pm2.io/
Add Control and Monitoring to Your Node.js Apps

View File

@ -8,7 +8,7 @@ var http = require('http');
var url = require('url');
var path = require('path');
var debug = require('debug')('pm2:serve');
var probe = require('pmx').probe();
var probe = require('@pm2/io').probe();
/**
* list of supported content types.

View File

@ -12,7 +12,6 @@ var exec = require('child_process').exec;
var Common = require('../Common.js');
var cst = require('../../constants.js');
var spawn = require('child_process').spawn;
var shelljs = require('shelljs');
module.exports = function(CLI) {
/**
@ -42,12 +41,13 @@ module.exports = function(CLI) {
'chkconfig' : 'systemv',
'rc-update' : 'openrc',
'launchctl' : 'launchd',
'sysrc' : 'rcd'
'sysrc' : 'rcd',
'rcctl' : 'rcd-openbsd',
};
var init_systems = Object.keys(hash_map);
for (var i = 0; i < init_systems.length; i++) {
if (shelljs.which(init_systems[i]) != null) {
if (require('shelljs').which(init_systems[i]) != null) {
break;
}
}
@ -148,9 +148,19 @@ module.exports = function(CLI) {
'sysrc -x ' + service_name + '_enable',
'rm /usr/local/etc/rc.d/' + service_name
];
break;
case 'rcd-openbsd':
service_name = (opts.serviceName || 'pm2_' + user);
var destination = path.join('/etc/rc.d', service_name);
commands = [
'rcctl stop ' + service_name,
'rcctl disable ' + service_name,
'rm ' + destination
];
break;
};
shelljs.exec(commands.join('&& '), function(code, stdout, stderr) {
require('shelljs').exec(commands.join('&& '), function(code, stdout, stderr) {
Common.printOut(stdout);
Common.printOut(stderr);
if (code == 0) {
@ -222,21 +232,9 @@ module.exports = function(CLI) {
else
template = getTemplate('systemd');
destination = '/etc/systemd/system/' + service_name + '.service';
commands = [
'systemctl enable ' + service_name
]
try {
fs.readFileSync(cst.PM2_PID_FILE_PATH).toString()
} catch(e) {
commands = [
'systemctl enable ' + service_name,
'systemctl start ' + service_name,
'systemctl daemon-reload',
'systemctl status ' + service_name
]
}
];
break;
case 'ubuntu14':
case 'ubuntu12':
@ -283,6 +281,17 @@ module.exports = function(CLI) {
'sysrc ' + service_name + '_enable=YES'
];
break;
case 'openbsd':
case 'rcd-openbsd':
template = getTemplate('rcd-openbsd');
service_name = (opts.serviceName || 'pm2_' + user);
destination = path.join('/etc/rc.d/', service_name);
commands = [
'chmod 755 ' + destination,
'rcctl enable ' + service_name,
'rcctl start ' + service_name
];
break;
case 'openrc':
template = getTemplate('openrc');
service_name = openrc_service_name;
@ -326,7 +335,7 @@ module.exports = function(CLI) {
async.forEachLimit(commands, 1, function(command, next) {
Common.printOut(cst.PREFIX_MSG + '[-] Executing: %s...', chalk.bold(command));
shelljs.exec(command, function(code, stdout, stderr) {
require('shelljs').exec(command, function(code, stdout, stderr) {
if (code === 0) {
Common.printOut(cst.PREFIX_MSG + chalk.bold('[v] Command successfully executed.'));
return next();
@ -380,13 +389,32 @@ module.exports = function(CLI) {
* @return
*/
function fin(err) {
// try to fix issues with empty dump file
// like #3485
if (env_arr.length === 0) {
// fix : if no dump file, no process, only module and after pm2 update
if (!fs.existsSync(cst.DUMP_FILE_PATH)) {
that.clearDump(function(){});
}
// if no process in list don't modify dump file
// process list should not be empty
if(cb) {
return cb(null, {success: true});
} else {
Common.printOut(cst.PREFIX_MSG + 'Nothing to save !!!');
Common.printOut(cst.PREFIX_MSG + 'In this case we keep old dump file. To clear dump file you can delete it manually !');
that.exitCli(cst.SUCCESS_EXIT);
return;
}
}
// Back up dump file
try {
if (fs.existsSync(cst.DUMP_FILE_PATH)) {
if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
fs.unlinkSync(cst.DUMP_BACKUP_FILE_PATH);
}
fs.renameSync(cst.DUMP_FILE_PATH, cst.DUMP_BACKUP_FILE_PATH);
fs.writeFileSync(cst.DUMP_BACKUP_FILE_PATH, fs.readFileSync(cst.DUMP_FILE_PATH));
}
} catch (e) {
console.error(e.stack || e);
@ -399,8 +427,13 @@ module.exports = function(CLI) {
} catch (e) {
console.error(e.stack || e);
try {
fs.unlinkSync(cst.DUMP_FILE_PATH);
// try to backup file
if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
fs.writeFileSync(cst.DUMP_FILE_PATH, fs.readFileSync(cst.DUMP_BACKUP_FILE_PATH));
}
} catch (e) {
// don't keep broken file
fs.unlinkSync(cst.DUMP_FILE_PATH);
console.error(e.stack || e);
}
Common.printOut(cst.PREFIX_MSG_ERR + 'Failed to save dump file in %s', cst.DUMP_FILE_PATH);
@ -424,6 +457,21 @@ module.exports = function(CLI) {
});
};
/**
* Remove DUMP_FILE_PATH file and DUMP_BACKUP_FILE_PATH file
* @method dump
* @param {} cb
* @return
*/
CLI.prototype.clearDump = function(cb) {
fs.writeFileSync(cst.DUMP_FILE_PATH, JSON.stringify([]));
if(cb && typeof cb === 'function') return cb();
Common.printOut(cst.PREFIX_MSG + 'Successfully created %s', cst.DUMP_FILE_PATH);
return this.exitCli(cst.SUCCESS_EXIT);
};
/**
* Resurrect processes
* @method resurrect

View File

@ -3,7 +3,6 @@ var cst = require('../../constants.js');
var Common = require('../Common.js');
var fs = require('fs');
var async = require('async');
var vizion = require('vizion');
var child = require('child_process');
var printError = Common.printError;
@ -21,11 +20,11 @@ module.exports = function(CLI) {
printOut(cst.PREFIX_MSG + 'Updating repository for process name %s', process_name);
that.Client.getProcessByName(process_name, function(err, processes) {
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
if (processes.length === 0) {
printError('No processes with this name: %s', process_name);
return cb ? cb({msg:'Process not found: '+process_name}) : that.exitCli(cst.ERROR_EXIT);
if (err || processes.length === 0) {
printError('No processes with this name or id : %s', process_name);
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
}
var proc = processes[0];
@ -33,7 +32,7 @@ module.exports = function(CLI) {
printOut(cst.PREFIX_MSG + 'No versioning system found for process %s', process_name);
return cb ? cb({success:false, msg: 'No versioning system found for process'}) : that.exitCli(cst.SUCCESS_EXIT);
}
vizion.update({
require('vizion').update({
folder: proc.pm2_env.versioning.repo_path
}, function(err, meta) {
if (err !== null) {
@ -82,19 +81,19 @@ module.exports = function(CLI) {
printOut(cst.PREFIX_MSG + 'Updating repository for process name %s', process_name);
that.Client.getProcessByName(process_name, function(err, processes) {
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
if (processes.length === 0) {
printError('No processes with this name: %s', process_name);
return cb ? cb({msg:'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
if (err || processes.length === 0) {
printError('No processes with this name or id : %s', process_name);
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
}
var proc = processes[0];
if (proc.pm2_env.versioning) {
vizion.isUpToDate({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
require('vizion').isUpToDate({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
if (err !== null)
return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT);
vizion.revertTo(
require('vizion').revertTo(
{revision: commit_id,
folder: proc.pm2_env.versioning.repo_path},
function(err2, meta2) {
@ -138,20 +137,22 @@ module.exports = function(CLI) {
var that = this;
printOut(cst.PREFIX_MSG + 'Downgrading to previous commit repository for process name %s', process_name);
that.Client.getProcessByName(process_name, function(err, processes) {
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
if (processes.length === 0) {
printError('No processes with this name: %s', process_name);
return cb ? cb({msg:'Process not found: '+process_name}) : that.exitCli(cst.ERROR_EXIT);
if (err || processes.length === 0) {
printError('No processes with this name or id : %s', process_name);
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
}
var proc = processes[0];
// in case user searched by id/pid
process_name = proc.name;
if (proc.pm2_env.versioning === undefined ||
proc.pm2_env.versioning === null)
return cb({msg : 'Versioning unknown'});
vizion.prev({
require('vizion').prev({
folder: proc.pm2_env.versioning.repo_path
}, function(err, meta) {
if (err)
@ -165,7 +166,7 @@ module.exports = function(CLI) {
getPostUpdateCmds(proc.pm2_env.versioning.repo_path, process_name, function (command_list) {
execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) {
if (err !== null) {
vizion.next({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
require('vizion').next({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
printError(err);
return cb ? cb({msg: meta.output + err}) : that.exitCli(cst.ERROR_EXIT);
});
@ -194,16 +195,18 @@ module.exports = function(CLI) {
var that = this;
printOut(cst.PREFIX_MSG + 'Updating to next commit repository for process name %s', process_name);
that.Client.getProcessByName(process_name, function(err, processes) {
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
if (processes.length === 0) {
printError('No processes with this name: %s', process_name);
return cb ? cb({msg:'Process not found: '+process_name}) : that.exitCli(cst.ERROR_EXIT);
if (err || processes.length === 0) {
printError('No processes with this name or id: %s', process_name);
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
}
var proc = processes[0];
// in case user searched by id/pid
process_name = proc.name;
if (proc.pm2_env.versioning) {
vizion.next({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
require('vizion').next({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
if (err !== null)
return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT);
if (meta.success === true) {
@ -211,7 +214,7 @@ module.exports = function(CLI) {
execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) {
if (err !== null)
{
vizion.prev({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
require('vizion').prev({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
printError(err);
return cb ? cb({msg:meta.output + err}) : that.exitCli(cst.ERROR_EXIT);
});
@ -366,16 +369,6 @@ module.exports = function(CLI) {
this._pull({process_name: process_name, action: 'reload'}, cb);
};
/**
* CLI method for updating a repository
* @method pullAndGracefulReload
* @param {string} process_name name of processes to pull
* @return
*/
CLI.prototype.pullAndGracefulReload = function (process_name, cb) {
this._pull({process_name: process_name, action: 'gracefulReload'}, cb);
};
/**
* CLI method for updating a repository to a specific commit id
* @method pullCommitId

View File

@ -2,23 +2,87 @@
"script": {
"type": "string",
"require": true,
"alias" : "exec"
"alias" : "exec",
"docDescription": "Path of the script to launch, required field"
},
"name": {
"type": "string",
"docDefault": "Script filename without the extension (app for app.js)",
"docDescription": "Process name in the process list"
},
"cwd": {
"type": "string",
"docDefault": "CWD of the current environment (from your shell)",
"docDescription": "Current working directory to start the process with"
},
"args": {
"type": [
"array",
"string"
]
],
"docDescription": "Arguments to pass to the script"
},
"exec_interpreter": {
"type": "string",
"alias": "interpreter",
"docDefault": "node",
"docDescription": "Interpreter absolute path"
},
"node_args": {
"type": [
"array",
"string"
],
"alias": ["interpreterArgs", "interpreter_args"]
"alias": ["interpreterArgs", "interpreter_args"],
"docDescription": "Arguments to pass to the interpreter"
},
"name": {
"type": "string"
"out_file": {
"type": "string",
"alias": ["out", "output", "out_log"],
"docDefault": "~/.pm2/logs/<app_name>-out.log",
"docDescription": "File path for stdout (each line is appended to this file)"
},
"error_file": {
"type": "string",
"alias": ["error", "err", "err_file", "err_log"],
"docDefault": "~/.pm2/logs/<app_name>-error.err",
"docDescription": "File path for stderr (each line is appended to this file)"
},
"log_file": {
"type": [
"boolean",
"string"
],
"alias": "log",
"docDefault": "/dev/null",
"docDescription": "File path for combined stdout and stderr (each line is appended to this file)"
},
"disable_logs": {
"type": "boolean",
"docDefault": false,
"docDescription": "Disable all logs storage"
},
"log_type": {
"type": "string",
"docDescription": "Define a specific log output type, possible value: json"
},
"log_date_format": {
"type": "string",
"docDescription": "Format for log timestamps in moment.js format (eg YYYY-MM-DD HH:mm Z)"
},
"env": {
"type": [
"object",
"string"
],
"docDescription": "Specify environment variables to be injected"
},
"^env_\\S*$": {
"type": [
"object",
"string"
],
"docDescription": "Specify environment variables to be injected when using --env <env_name>"
},
"max_memory_restart": {
"type": [
@ -27,124 +91,91 @@
],
"regex": "^\\d+(G|M|K)?$",
"ext_type": "sbyte",
"desc": "it should be a NUMBER - byte, \"[NUMBER]G\"(Gigabyte), \"[NUMBER]M\"(Megabyte) or \"[NUMBER]K\"(Kilobyte)"
},
"uid" : {
"type" : "string"
},
"gid" : {
"type" : "string"
},
"restart_delay": {
"type" : "number"
},
"source_map_support" : {
"type": "boolean"
},
"wait_ready" : {
"type": "boolean"
},
"disable_source_map_support" : {
"type": "boolean"
},
"instances": {
"type": "number"
},
"kill_timeout": {
"type": "number"
},
"listen_timeout": {
"type": "number"
},
"port": {
"type": "number"
},
"log_file": {
"type": [
"boolean",
"string"
],
"alias": "log"
},
"error_file": {
"type": "string",
"alias": ["error", "err", "err_file", "err_log"]
},
"log_type": {
"type": "string"
},
"out_file": {
"type": "string",
"alias": ["output", "out", "out_log"]
"desc": "it should be a NUMBER - byte, \"[NUMBER]G\"(Gigabyte), \"[NUMBER]M\"(Megabyte) or \"[NUMBER]K\"(Kilobyte)",
"docDescription": "Restart the app if an amount of memory is exceeded (format: /[0-9](K&#124;M&#124;G)?/ K for KB, 'M' for MB, 'G' for GB, default to B)"
},
"pid_file": {
"type": "string",
"alias": "pid"
"alias": "pid",
"docDefault": "~/.pm2/pids/app_name-id.pid",
"docDescription": "File path where the pid of the started process is written by pm2"
},
"restart_delay": {
"type" : "number",
"docDefault": 0,
"docDescription": "Time in ms to wait before restarting a crashing app"
},
"source_map_support": {
"type": "boolean",
"docDefault": true,
"docDescription": "Enable or disable the source map support"
},
"disable_source_map_support": {
"type": "boolean",
"docDefault": false,
"docDescription": "Enable or disable the source map support"
},
"wait_ready": {
"type": "boolean",
"docDefault": false,
"docDescription": "Make the process wait for a process.send('ready')"
},
"instances": {
"type": "number",
"docDefault": 1,
"docDescription": "Number of instances to be started in cluster mode"
},
"kill_timeout": {
"type": "number",
"docDefault": 1600,
"docDescription": "Time in ms before sending the final SIGKILL signal after SIGINT"
},
"listen_timeout": {
"type": "number",
"docDescription": "Time in ms before forcing a reload if app is still not listening/has still note sent ready"
},
"cron_restart": {
"type": "string",
"alias": "cron"
},
"cwd": {
"type": "string"
"alias": "cron",
"docDescription": "A cron pattern to restart your app"
},
"merge_logs": {
"type": "boolean",
"alias" : "combine_logs"
"alias" : "combine_logs",
"docDefault": false,
"docDescription": "In cluster mode, merge each type of logs into a single file (instead of having one for each cluster)"
},
"vizion" : {
"vizion": {
"type": "boolean",
"default" : true
"default" : true,
"docDefault" : "True",
"docDescription": "Enable or disable the versioning metadatas (vizion library)"
},
"pmx" : {
"autorestart": {
"type": "boolean",
"default" : true
},
"automation" : {
"type": "boolean",
"default" : true
},
"autorestart" : {
"type": "boolean",
"default" : true
},
"treekill" : {
"type": "boolean",
"default" : true
"default": true,
"docDefault": "True",
"docDescription": "Enable or disable auto restart after process failure"
},
"watch": {
"type": [
"boolean",
"array",
"string"
]
],
"docDefault": false,
"docDescription": "Enable or disable the watch mode"
},
"ignore_watch": {
"type": [
"array",
"string"
]
],
"docDescription": "List of paths to ignore (regex)"
},
"watch_options": {
"type": "object"
},
"env": {
"type": [
"object",
"string"
]
},
"^env_\\S*$": {
"type": [
"object",
"string"
]
},
"disable_logs" : {
"type": "boolean"
},
"log_date_format": {
"type": "string"
"type": "object",
"docDescription": "Object that will be used as an options with chokidar (refer to chokidar documentation)"
},
"min_uptime": {
"type": [
@ -154,43 +185,51 @@
"regex": "^\\d+(h|m|s)?$",
"desc": "it should be a NUMBER - milliseconds, \"[NUMBER]h\"(hours), \"[NUMBER]m\"(minutes) or \"[NUMBER]s\"(seconds)",
"min": 100,
"ext_type": "stime"
"ext_type": "stime",
"docDefault": 1000,
"docDescription": "Minimum uptime of the app to be considered started (format is /[0-9]+(h&#124;m&#124;s)?/, for hours, minutes, seconds, docDefault to ms)"
},
"max_restarts": {
"type": "number",
"min": 0
"min": 0,
"docDefault": 16,
"docDescription": "Number of times a script is restarted when it exits in less than min_uptime"
},
"exec_mode": {
"type": "string",
"regex": "^(cluster|fork)(_mode)?$",
"alias": "executeCommand",
"desc": "it should be \"cluster\"(\"cluster_mode\") or \"fork\"(\"fork_mode\") only"
},
"exec_interpreter": {
"type": "string",
"alias": "interpreter"
},
"write": {
"type": "boolean"
"desc": "it should be \"cluster\"(\"cluster_mode\") or \"fork\"(\"fork_mode\") only",
"docDefault": "fork",
"docDescription": "Set the execution mode, possible values: fork&#124;cluster"
},
"force": {
"type": "boolean"
"type": "boolean",
"docDefault": false,
"docDescription": "Start a script even if it is already running (only the script path is considered)"
},
"append_env_to_name": {
"type": "boolean"
"type": "boolean",
"docDefault": false,
"docDescription": "Append the environment name to the app name"
},
"post_update": {
"type": "array"
},
"disable_trace": {
"type": [
"boolean"
]
"type": "array",
"docDescription": "List of commands executed after a pull/upgrade operation performed from Keymetrics dashboard"
},
"trace": {
"type": [
"boolean"
]
],
"docDefault": false,
"docDescription": "Enable or disable the transaction tracing"
},
"disable_trace": {
"type": [
"boolean"
],
"docDefault": true,
"docDescription": "Enable or disable the transaction tracing"
},
"v8": {
"type": [
@ -208,14 +247,66 @@
]
},
"increment_var": {
"type": "string"
"type": "string",
"docDescription": "Specify the name of an environment variable to inject which increments for each cluster"
},
"instance_var": {
"type": "string",
"default" : "NODE_APP_INSTANCE"
},
"default": "NODE_APP_INSTANCE",
"docDefault": "NODE_APP_INSTANCE",
"docDescription": "Rename the NODE_APP_INSTANCE environment variable"
},
"pmx": {
"type": "boolean",
"default": true,
"docDefault": "True",
"docDescription": "Enable or disable pmx wrapping"
},
"automation": {
"type": "boolean",
"default": true,
"docDefault": "True",
"docDescription": "Enable or disable pmx wrapping"
},
"treekill": {
"type": "boolean",
"default": true,
"docDefault": "True",
"docDescription": "Only kill the main process, not detached children"
},
"port": {
"type": "number",
"docDescription": "Shortcut to inject a PORT environment variable"
},
"username" : {
"type": "string",
"docDescription": "Current user that started the process"
},
"uid": {
"type" : "string",
"docDefault": "Current user uid",
"docDescription": "Set user id"
},
"gid": {
"type" : "string",
"docDefault": "Current user gid",
"docDescription": "Set group id"
},
"windowsHide": {
"type": "boolean",
"default" : true
"docDefault": "True",
"docDescription": "Enable or disable the Windows popup when starting an app",
"default": true
},
"kill_retry_time": {
"type": "number",
"default" : 100
},
"write": {
"type": "boolean"
},
"io": {
"type": "object",
"docDescription": "Specify apm values and configuration"
}
}

View File

@ -6,15 +6,14 @@
var debug = require('debug')('pm2:client');
var Common = require('./Common.js');
var KMDaemon = require('./Interactor/InteractorDaemonizer.js');
var KMDaemon = require('@pm2/agent/src/InteractorClient');
var rpc = require('pm2-axon-rpc');
var async = require('async');
var axon = require('pm2-axon');
var util = require('util');
var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var shelljs = require('shelljs');
var pkg = require('../package.json')
function noop() {}
@ -79,7 +78,8 @@ Client.prototype.start = function(cb) {
KMDaemon.launchAndInteract(that.conf, {
machine_name : that.machine_name,
public_key : that.public_key,
secret_key : that.secret_key
secret_key : that.secret_key,
pm2_version : pkg.version
}, function(err, data, interactor_proc) {
that.interactor_process = interactor_proc;
});
@ -129,7 +129,7 @@ Client.prototype.start = function(cb) {
Client.prototype.initFileStructure = function (opts) {
if (!fs.existsSync(opts.DEFAULT_LOG_PATH)) {
try {
mkdirp.sync(opts.DEFAULT_LOG_PATH);
require('mkdirp').sync(opts.DEFAULT_LOG_PATH);
} catch (e) {
console.error(e.stack || e);
}
@ -137,7 +137,7 @@ Client.prototype.initFileStructure = function (opts) {
if (!fs.existsSync(opts.DEFAULT_PID_PATH)) {
try {
mkdirp.sync(opts.DEFAULT_PID_PATH);
require('mkdirp').sync(opts.DEFAULT_PID_PATH);
} catch (e) {
console.error(e.stack || e);
}
@ -153,7 +153,7 @@ Client.prototype.initFileStructure = function (opts) {
if (!fs.existsSync(opts.DEFAULT_MODULE_PATH)) {
try {
mkdirp.sync(opts.DEFAULT_MODULE_PATH);
require('mkdirp').sync(opts.DEFAULT_MODULE_PATH);
} catch (e) {
console.error(e.stack || e);
}
@ -241,7 +241,7 @@ Client.prototype.launchDaemon = function(opts, cb) {
var interpreter = 'node';
if (shelljs.which('node') == null)
if (require('shelljs').which('node') == null)
interpreter = process.execPath;
var child = require('child_process').spawn(interpreter, node_args, {
@ -278,7 +278,8 @@ Client.prototype.launchDaemon = function(opts, cb) {
KMDaemon.launchAndInteract(that.conf, {
machine_name : that.machine_name,
public_key : that.public_key,
secret_key : that.secret_key
secret_key : that.secret_key,
pm2_version : pkg.version
}, function(err, data, interactor_proc) {
that.interactor_process = interactor_proc;
return cb(null, child);
@ -717,3 +718,25 @@ Client.prototype.getProcessByName = function(name, cb) {
return cb(null, found_proc);
});
};
Client.prototype.getProcessByNameOrId = function (nameOrId, cb) {
var foundProc = [];
this.executeRemote('getMonitorData', {}, function (err, list) {
if (err) {
Common.printError('Error retrieving process list: ' + err);
return cb(err);
}
list.forEach(function (proc) {
if (proc.pm2_env.name === nameOrId ||
proc.pm2_env.pm_exec_path === path.resolve(nameOrId) ||
proc.pid === parseInt(nameOrId) ||
proc.pm2_env.pm_id === parseInt(nameOrId)) {
foundProc.push(proc);
}
});
return cb(null, foundProc);
});
};

View File

@ -12,9 +12,7 @@ var fs = require('fs');
var path = require('path');
var os = require('os');
var util = require('util');
var mkdirp = require('mkdirp');
var async = require('async');
var shelljs = require('shelljs');
var chalk = require('chalk');
var fclone = require('fclone');
var semver = require('semver');
@ -23,7 +21,6 @@ var isBinary = require('./tools/isbinaryfile.js');
var cst = require('../constants.js');
var extItps = require('./API/interpreter.json');
var Config = require('./tools/Config');
var KMDaemon = require('./Interactor/InteractorDaemonizer.js');
var Common = module.exports;
@ -151,7 +148,7 @@ Common.prepareAppConf = function(opts, app) {
if (!fs.existsSync(app.pm_exec_path)) {
var ckd;
// Try resolve command available in $PATH
if ((ckd = shelljs.which(app.script))) {
if ((ckd = require('shelljs').which(app.script))) {
if (typeof(ckd) !== 'string')
ckd = ckd.toString();
app.pm_exec_path = ckd;
@ -226,7 +223,7 @@ Common.prepareAppConf = function(opts, app) {
if (!fs.existsSync(dir)) {
Common.printError(cst.PREFIX_MSG_WARNING + 'Folder does not exists: ' + dir);
Common.printOut(cst.PREFIX_MSG + 'Creating folder: ' + dir);
mkdirp(dir, function(err) {
require('mkdirp')(dir, function(err) {
if (!err) return;
Common.printError(cst.PREFIX_MSG_ERR + 'Could not create folder: ' + path.dirname(af));
throw new Error('Could not create folder');
@ -247,20 +244,22 @@ Common.prepareAppConf = function(opts, app) {
* @param {string} filename
* @return {mixed} null if not conf file, json or yaml if conf
*/
Common.isConfigFile = function(filename) {
if (typeof(filename) != 'string')
Common.isConfigFile = function (filename) {
if (typeof (filename) !== 'string')
return null;
if (filename.indexOf('.json') != -1)
if (filename.indexOf('.json') !== -1)
return 'json';
if (filename.indexOf('.yml') > -1 || filename.indexOf('.yaml') > -1)
return 'yaml';
if (filename.indexOf('.config.js') != -1)
if (filename.indexOf('.config.js') !== -1)
return 'js';
if (filename.indexOf('.config.mjs') !== -1)
return 'mjs';
return null;
};
/**
* Parses a config file like ecosystem.json. Supported formats: JS, JSON, JSON5, YAML.
* Parses a config file like ecosystem.config.js. Supported formats: JS, JSON, JSON5, YAML.
* @param {string} confString contents of the config file
* @param {string} filename path to the config file
* @return {Object} config object
@ -288,7 +287,7 @@ Common.parseConfig = function(confObj, filename) {
filename.indexOf('.yaml') > -1) {
return yamljs.parse(confObj.toString());
}
else if (filename.indexOf('.config.js') > -1) {
else if (filename.indexOf('.config.js') > -1 || filename.indexOf('.config.mjs') > -1) {
var confPath = require.resolve(path.resolve(filename));
delete require.cache[confPath];
return require(confPath);
@ -366,7 +365,7 @@ var resolveNodeInterpreter = function(app) {
var nvm_cmd = '. ' + nvm_bin + ' ; nvm install ' + node_version;
Common.printOut(cst.PREFIX_MSG + 'Executing: %s', nvm_cmd);
shelljs.exec(nvm_cmd);
require('shelljs').exec(nvm_cmd);
}
Common.printOut(cst.PREFIX_MSG + chalk.green.bold('Setting Node to v%s (path=%s)'),
@ -409,7 +408,7 @@ Common.sink.resolveInterpreter = function(app) {
app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/coffee');
}
if (app.exec_interpreter != 'none' && shelljs.which(app.exec_interpreter) == null) {
if (app.exec_interpreter != 'none' && require('shelljs').which(app.exec_interpreter) == null) {
Common.printError(cst.PREFIX_MSG_ERR + 'Interpreter ' + app.exec_interpreter + ' does not seem to be available');
}
return app;
@ -585,9 +584,34 @@ Common.verifyConfs = function(appConfs){
for (var i = 0; i < appConfs.length; i++) {
var app = appConfs[i];
if (app.disable_trace) {
app.trace = false
delete app.disable_trace;
// JSON conf: alias cmd to script
if (app.cmd && !app.script) {
app.script = app.cmd
delete app.cmd
}
// JSON conf: alias command to script
if (app.command && !app.script) {
app.script = app.command
delete app.command
}
app.username = Common.getCurrentUsername();
// If command is like pm2 start "python xx.py --ok"
// Then automatically start the script with bash -c and set a name eq to command
if (app.script && app.script.indexOf(' ') > -1) {
var _script = app.script;
if (require('shelljs').which('bash'))
app.script = 'bash';
else if (require('shelljs').which('sh'))
app.script = 'sh';
else
throw new Error('bash and sh not available in $PATH')
app.args = ['-c', _script];
if (!app.name) {
app.name = _script
}
}
if ((app.uid || app.gid) && app.force !== true) {
@ -597,25 +621,34 @@ Common.verifyConfs = function(appConfs){
}
}
// If no uid set and command runned as sudo, use the parent shell USER
// to set it to his uid and not root
// if (!app.uid && process.env.SUDO_USER) {
// app.uid = process.env.SUDO_USER;
// }
// Specific options of PM2.io
if (process.env.PM2_DEEP_MONITORING)
app.deep_monitoring = true;
// Warn deprecates.
checkDeprecates(app);
if (app.disable_trace) {
app.trace = false
delete app.disable_trace;
}
if (app.instances == 'max')
app.instances = 0;
// Sanity check, default to number of cores if value can't be parsed
if (typeof(app.instances) === 'string')
app.instances = parseInt(app.instances) || 0;
// Check Exec mode
checkExecMode(app);
if (app.exec_mode != 'cluster_mode' &&
!app.instances && typeof(app.merge_logs) == 'undefined')
app.merge_logs = true;
// Render an app name if not existing.
prepareAppName(app);
var ret = Config.validateJSON(app);
//debug('After processing', ret);
// Show errors if existing.
// Show errors if existing.
if (ret.errors && ret.errors.length > 0){
ret.errors.forEach(function(err){
warn(err);
@ -689,18 +722,6 @@ function checkExecMode(conf) {
}
}
/**
* Check deprecates and show warnings.
* @param {Object} conf
*/
function checkDeprecates(conf){
if (conf.instances == 'max')
conf.instances = 0;
// Sanity check, default to number of cores if value can't be parsed
if (typeof(conf.instances) === 'string')
conf.instances = parseInt(conf.instances) || 0;
}
/**
* Render an app name if not existing.
* @param {Object} conf

View File

@ -153,7 +153,7 @@ Daemon.prototype.innerStart = function(cb) {
var profiler;
try {
profiler = require('v8-profiler');
profiler = require('v8-profiler-node8');
} catch(e) {
profiler = null;
}
@ -231,7 +231,6 @@ Daemon.prototype.innerStart = function(cb) {
notifyByProcessId : God.notifyByProcessId,
notifyKillPM2 : God.notifyKillPM2,
forceGc : God.forceGc,
monitor : God.monitor,
unmonitor : God.unmonitor,

View File

@ -20,7 +20,6 @@ var numCPUs = require('os').cpus() ? require('os').cpus().length : 1;
var path = require('path');
var EventEmitter2 = require('eventemitter2').EventEmitter2;
var fs = require('fs');
var pidusage = require('pidusage');
var vizion = require('vizion');
var debug = require('debug')('pm2:god');
var Utility = require('./Utility');
@ -291,9 +290,6 @@ God.handleExit = function handleExit(clu, exit_code, kill_signal) {
return false;
}
if (proc.process.pid)
pidusage.unmonitor(proc.process.pid);
var stopping = (proc.pm2_env.status == cst.STOPPING_STATUS
|| proc.pm2_env.status == cst.STOPPED_STATUS
|| proc.pm2_env.status == cst.ERRORED_STATUS) || (proc.pm2_env.autorestart === false ||
@ -384,6 +380,9 @@ God.handleExit = function handleExit(clu, exit_code, kill_signal) {
* Init new process
*/
God.prepare = function prepare (env, cb) {
// generate a new unique id for each processes
env.env.unique_id = Utility.generateUUID()
// if the app is standalone, no multiple instance
if (typeof env.instances === 'undefined') {
env.vizion_running = false;

View File

@ -40,55 +40,63 @@ module.exports = function(God) {
*/
God.getMonitorData = function getMonitorData(env, cb) {
var processes = God.getFormatedProcesses();
var pids = processes.filter(filterBadProcess)
.map(function(pro, i) {
var pid = getProcessId(pro)
return pid;
})
async.eachSeries(processes, function computeMonitor(pro, next) {
if (pro.pm2_env.status == cst.ONLINE_STATUS) {
var pid = pro.pid;
if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) {
if (isNaN(pro.pm2_env.axm_options.pid)) {
pro['monit'] = {
memory : 0,
cpu : 0
};
return process.nextTick(next);
}
pid = pro.pm2_env.axm_options.pid;
}
pidusage.stat(pid, function retPidUsage(err, res) {
if (err) {
// Do not log, some time modules does not retrieve PID
// console.error('Error caught while calling pidusage');
// console.error(err);
pro['monit'] = {
memory : 0,
cpu : 0
};
return next();
}
pro['monit'] = {
memory : Math.floor(res.memory),
cpu : Math.floor(res.cpu)
};
res = null;
pid = null;
return next();
});
}
else {
// No pids, return empty statistics
if (pids.length === 0) {
return cb(null, processes.map(function(pro) {
pro['monit'] = {
memory : 0,
cpu : 0
};
return next();
}
}, function retMonitor(err, res) {
if (err) return cb(God.logAndGenerateError(err), null);
return cb(null, processes);
});
return pro
}))
}
pidusage(pids, function retPidUsage(err, statistics) {
// Just log, we'll set empty statistics
if (err) {
console.error('Error caught while calling pidusage');
console.error(err);
}
processes = processes.map(function(pro) {
if (filterBadProcess(pro) === false) {
pro['monit'] = {
memory : 0,
cpu : 0
};
return pro;
}
var pid = getProcessId(pro);
var stat = statistics[pid];
if (!stat) {
pro['monit'] = {
memory : 0,
cpu : 0
};
return pro;
}
pro['monit'] = {
memory: stat.memory,
cpu: Math.round(stat.cpu * 10) / 10
};
return pro;
});
cb(null, processes);
});
};
/**
@ -126,6 +134,7 @@ module.exports = function(God) {
God.dumpProcessList = function(cb) {
var process_list = [];
var apps = Utility.clone(God.getFormatedProcesses());
var that = this;
// Don't override the actual dump file if process list is empty
// unless user explicitely did `pm2 dump`.
@ -137,13 +146,25 @@ module.exports = function(God) {
}
function fin(err) {
// try to fix issues with empty dump file
// like #3485
if (process_list.length === 0) {
// fix : if no dump file, no process, only module and after pm2 update
if (!fs.existsSync(cst.DUMP_FILE_PATH)) {
that.clearDump(function(){});
}
// if no process in list don't modify dump file
// process list should not be empty
return cb(null, {success:true, process_list: process_list});
}
// Back up dump file
try {
if (fs.existsSync(cst.DUMP_FILE_PATH)) {
if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
fs.unlinkSync(cst.DUMP_BACKUP_FILE_PATH);
}
fs.renameSync(cst.DUMP_FILE_PATH, cst.DUMP_BACKUP_FILE_PATH);
fs.writeFileSync(cst.DUMP_BACKUP_FILE_PATH, fs.readFileSync(cst.DUMP_FILE_PATH));
}
} catch (e) {
console.error(e.stack || e);
@ -155,8 +176,13 @@ module.exports = function(God) {
} catch (e) {
console.error(e.stack || e);
try {
fs.unlinkSync(cst.DUMP_FILE_PATH);
// try to backup file
if(fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
fsExtra.copySync(cst.DUMP_BACKUP_FILE_PATH, cst.DUMP_FILE_PATH);
}
} catch (e) {
// don't keep broken file
fs.unlinkSync(cst.DUMP_FILE_PATH);
console.error(e.stack || e);
}
}
@ -213,8 +239,13 @@ module.exports = function(God) {
var proc = Utility.clone(God.clusters_db[id].pm2_env);
delete proc.created_at;
delete proc.pm_id;
delete proc.unique_id;
// generate a new unique id for new process
proc.unique_id = Utility.generateUUID()
God.injectVariables(proc, function inject (_err, proc) {
return God.executeApp(Utility.clone(proc), function (err, clu) {
@ -264,17 +295,19 @@ module.exports = function(God) {
if (!(id in God.clusters_db))
return cb(God.logAndGenerateError(id + ' : id unknown'), {});
//clear time-out restart task
clearTimeout(God.clusters_db[id].pm2_env.restart_task);
if (God.clusters_db[id].pm2_env.status == cst.STOPPED_STATUS)
return cb(null, God.getFormatedProcess(id));
// state == 'none' means that the process is not online yet
if (God.clusters_db[id].state && God.clusters_db[id].state === 'none')
return setTimeout(function() { God.stopProcessId(id, cb); }, 250);
var proc = God.clusters_db[id];
//clear time-out restart task
clearTimeout(proc.pm2_env.restart_task);
if (proc.pm2_env.status == cst.STOPPED_STATUS) {
proc.process.pid = 0;
return cb(null, God.getFormatedProcess(id));
}
// state == 'none' means that the process is not online yet
if (proc.state && proc.state === 'none')
return setTimeout(function() { God.stopProcessId(id, cb); }, 250);
console.log('Stopping app:%s id:%s', proc.pm2_env.name, proc.pm2_env.pm_id);
proc.pm2_env.status = cst.STOPPING_STATUS;
@ -286,7 +319,6 @@ module.exports = function(God) {
God.killProcess(proc.process.pid, proc.pm2_env, function(err) {
proc.pm2_env.status = cst.STOPPED_STATUS;
pidusage.unmonitor(proc.process.pid);
God.notify('exit', proc);
@ -823,3 +855,27 @@ module.exports = function(God) {
});
};
};
function filterBadProcess(pro) {
if (pro.pm2_env.status !== cst.ONLINE_STATUS) {
return false;
}
if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) {
if (isNaN(pro.pm2_env.axm_options.pid)) {
return false;
}
}
return true;
}
function getProcessId(pro) {
var pid = pro.pid
if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) {
pid = pro.pm2_env.axm_options.pid;
}
return pid
}

View File

@ -167,9 +167,9 @@ module.exports = function(God) {
clearInterval(timer);
return cb(null, true);
}
console.log('pid=%d msg=failed to kill - retrying in 100ms', pid);
console.log('pid=%d msg=failed to kill - retrying in %dms', pid, pm2_env.kill_retry_time);
return false;
}, 100);
}, pm2_env.kill_retry_time);
timeout = setTimeout(function() {
clearInterval(timer);
@ -247,21 +247,4 @@ module.exports = function(God) {
pm2_env.unstable_restarts = 0;
};
/**
* Description
* @method forcegc
* @return
*/
God.forceGc = function(opts, cb) {
if (global.gc) {
global.gc();
debug('Garbage collection triggered successfully');
if (cb) cb(null, {success: true});
}
else {
debug('Garbage collection failed');
if (cb) cb(null, {success: false});
}
};
};

View File

@ -174,7 +174,7 @@ function hardReload(God, id, wait_msg, cb) {
module.exports = function(God) {
/**
* GracefulReload
* Reload
* @method softReloadProcessId
* @param {} id
* @param {} cb

View File

@ -1,50 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var crypto = require('crypto');
const CIPHER_ALGORITHM = 'aes256';
var Cipher = module.exports = {};
/**
* Description
* @method decipherMessage
* @param {} msg
* @return ret
*/
Cipher.decipherMessage = function(msg, key) {
var ret = {};
try {
var decipher = crypto.createDecipher(CIPHER_ALGORITHM, key);
var decipheredMessage = decipher.update(msg, 'hex', 'utf8');
decipheredMessage += decipher.final('utf8');
ret = JSON.parse(decipheredMessage);
} catch(e) {
return null;
}
return ret;
}
/**
* Description
* @method cipherMessage
* @param {} data
* @param {} key
* @return
*/
Cipher.cipherMessage = function(data, key) {
try {
var cipher = crypto.createCipher(CIPHER_ALGORITHM, key);
var cipheredData = cipher.update(data, 'utf8', 'hex');
cipheredData += cipher.final('hex');
return cipheredData;
} catch(e) {
return null;
}
}

View File

@ -1,443 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var fs = require('fs');
var ipm2 = require('./pm2-interface.js');
var rpc = require('pm2-axon-rpc');
var axon = require('pm2-axon');
var debug = require('debug')('interface:driver'); // Interface
var chalk = require('chalk');
var Url = require('url');
var os = require('os');
var domain = require('domain');
var fmt = require('../tools/fmt.js');
var pkg = require('../../package.json');
var PM2 = require('../..');
var cst = require('../../constants.js');
var Cipher = require('./Cipher.js');
var ReverseInteractor = require('./ReverseInteractor.js');
var PushInteractor = require('./PushInteractor.js');
var Utility = require('../Utility.js');
var WatchDog = require('./WatchDog.js');
var Conf = require('../Configuration.js');
var HttpRequest = require('./HttpRequest.js');
var InternalIP = require('./internal-ip.js');
global._pm2_password_protected = false;
// Flag for log streaming status
global._logs = false;
var Daemon = module.exports = {
connectToPM2 : function() {
return ipm2();
},
exit : function() {
var self = this;
this.opts.pm2_instance.disconnect(function() {
console.log('Connection to PM2 via CLI closed');
});
process.nextTick(function() {
try {
fs.unlinkSync(cst.INTERACTOR_RPC_PORT);
fs.unlinkSync(cst.INTERACTOR_PID_PATH);
} catch(e) {}
if (self.opts.ipm2)
self.opts.ipm2.disconnect();
console.log('Exiting Interactor');
if (!this._rpc || !this._rpc.sock)
return process.exit(cst.ERROR_EXIT);
this._rpc.sock.close(function() {
console.log('RPC closed - Interactor killed');
process.exit(cst.SUCCESS_EXIT);
});
});
},
activateRPC : function() {
console.log('Launching Interactor exposure');
var self = this;
var rep = axon.socket('rep');
var daemon_server = new rpc.Server(rep);
var sock = rep.bind(cst.INTERACTOR_RPC_PORT);
daemon_server.expose({
kill : function(cb) {
console.log('Killing interactor');
cb(null);
return Daemon.exit();
},
passwordSet : function(cb) {
global._pm2_password_protected = true;
return cb(null);
},
getInfos : function(cb) {
if (self.opts &&
self.opts.DAEMON_ACTIVE == true)
return cb(null, {
machine_name : self.opts.MACHINE_NAME,
public_key : self.opts.PUBLIC_KEY,
secret_key : self.opts.SECRET_KEY,
remote_host : cst.REMOTE_HOST,
remote_port : cst.REMOTE_PORT,
reverse_interaction : self.opts.REVERSE_INTERACT,
socket_path : cst.INTERACTOR_RPC_PORT,
pm2_home_monitored : cst.PM2_HOME
});
else {
return cb(null);
}
}
});
return daemon_server;
},
formatMetada : function() {
var cpu, memory;
var self = this;
try {
cpu = os.cpus();
memory = Math.floor(os.totalmem() / 1024 / 1024);
} catch(e) {
cpu = 0;
memory = 0;
};
var ciphered_data = Cipher.cipherMessage(JSON.stringify({
MACHINE_NAME : this.opts.MACHINE_NAME,
PUBLIC_KEY : this.opts.PUBLIC_KEY,
PM2_VERSION : this.opts.PM2_VERSION,
RECYCLE : this.opts.RECYCLE || false,
MEMORY : memory,
HOSTNAME : os.hostname(),
CPUS : cpu.length
}), this.opts.SECRET_KEY);
return ciphered_data;
},
pingKeepAlive : function() {
var self = this;
(function checkInternet() {
require('dns').lookup('google.com',function(err) {
if (err && (err.code == 'ENOTFOUND' || err.code == 'EAI_AGAIN')) {
if (self.opts._connection_is_up == true)
console.error('[CRITICAL] Internet is unreachable (via DNS lookup strategy)');
self.opts._connection_is_up = false;
} else {
if (self.opts._connection_is_up == false) {
console.log('[TENTATIVE] Reactivating connection');
PushInteractor.connectRemote();
ReverseInteractor.reconnect();
}
self.opts._connection_is_up = true;
}
setTimeout(checkInternet, 15000);
});
})();
},
changeUrls : function(push_url, reverse) {
if (push_url)
PushInteractor.connectRemote(push_url);
if (reverse)
ReverseInteractor.changeUrl(reverse);
},
refreshWorker : function() {
var self = this;
function refreshMetadata() {
var ciphered_data = Daemon.formatMetada();
HttpRequest.post({
url : self.opts.ROOT_URL,
port : self.opts.ROOT_PORT,
data : {
public_id : self.opts.PUBLIC_KEY,
data : ciphered_data
}
}, function(err, km_data) {
if (err) return console.error(err);
/** protect against malformated data **/
if (!km_data ||
!km_data.endpoints ||
!km_data.endpoints.push ||
!km_data.endpoints.reverse) {
console.error('[CRITICAL] Malformated data received, skipping...');
return false;
}
/**************************************
* Urls has changed = update workers *
**************************************/
if ((Daemon.current_km_data.endpoints.push != km_data.endpoints.push) ||
(Daemon.current_km_data.endpoints.reverse != km_data.endpoints.reverse)) {
self.changeUrls(km_data.endpoints.push, km_data.endpoints.reverse);
Daemon.current_km_data = km_data;
}
else {
debug('[REFRESH META] No need to update URL (same)', km_data);
}
return false;
});
};
// Refresh metadata every minutes
setInterval(function() {
refreshMetadata();
}, 60000);
},
validateData : function() {
var opts = {};
opts.MACHINE_NAME = process.env.PM2_MACHINE_NAME;
opts.PUBLIC_KEY = process.env.PM2_PUBLIC_KEY;
opts.SECRET_KEY = process.env.PM2_SECRET_KEY;
opts.RECYCLE = process.env.KM_RECYCLE ? JSON.parse(process.env.KM_RECYCLE) : false;
opts.REVERSE_INTERACT = JSON.parse(process.env.PM2_REVERSE_INTERACT);
opts.PM2_VERSION = pkg.version;
if (!opts.MACHINE_NAME) {
console.error('You must provide a PM2_MACHINE_NAME environment variable');
process.exit(cst.ERROR_EXIT);
}
else if (!opts.PUBLIC_KEY) {
console.error('You must provide a PM2_PUBLIC_KEY environment variable');
process.exit(cst.ERROR_EXIT);
}
else if (!opts.SECRET_KEY) {
console.error('You must provide a PM2_SECRET_KEY environment variable');
process.exit(cst.ERROR_EXIT);
}
return opts;
},
welcome : function(cb) {
var self = this;
var ciphered_data = Daemon.formatMetada();
if (!ciphered_data) {
process.send({
msg : 'Error while ciphering data',
error : true
});
return process.exit(1);
}
var retries = 0;
function doWelcomeQuery(cb) {
HttpRequest.post({
url : self.opts.ROOT_URL,
data : {
public_id : self.opts.PUBLIC_KEY,
data : ciphered_data
}
}, function(err, km_data) {
self.current_km_data = km_data;
if (err) {
console.error('Got error while connecting: %s', err.message || err);
if (retries < 30) {
retries++;
setTimeout(function() {
doWelcomeQuery(cb);
}, 200 * retries);
return false;
}
return cb(err);
}
if (self.opts.RECYCLE) {
if (!km_data.name) {
console.error('Error no previous machine name for recycle option returned!');
}
self.opts.MACHINE_NAME = km_data.name;
};
// For Human feedback
if (process.send) {
try {
process.send({
error : false,
km_data : km_data,
online : true,
pid : process.pid,
machine_name : self.opts.MACHINE_NAME,
public_key : self.opts.PUBLIC_KEY,
secret_key : self.opts.SECRET_KEY,
reverse_interaction : self.opts.REVERSE_INTERACT
});
} catch(e) {
// Just in case the CLI has been disconected
}
}
// Return get data
return cb(null, km_data);
})
}
doWelcomeQuery(function(err, meta) {
return cb(err, meta);
});
},
protectedStart : function() {
var self = this;
var d = domain.create();
d.once('error', function(err) {
fmt.sep();
fmt.title('Agent global error caught');
fmt.field('Time', new Date());
console.error(err.message);
console.error(err.stack);
fmt.sep();
console.error('[Agent] Resurrecting');
var KMDaemon = require('../Interactor/InteractorDaemonizer');
KMDaemon.rescueStart(cst, function(err, dt) {
if (err) {
console.error('[Agent] Failed to rescue agent, error:');
console.error(err.message || err);
process.exit(1);
}
console.log('[Agent] Rescued.');
process.exit(0);
});
});
d.run(function() {
self.start();
});
},
start : function() {
var self = this;
self.opts = self.validateData();
self.opts.ipm2 = null;
self.opts.internal_ip = InternalIP();
self.opts.pm2_instance = PM2;
self.opts._connection_is_up = true;
self.current_km_data = null;
self.opts.pm2_instance.connect(function() {
console.log('Connected to PM2');
});
self._rpc = self.activateRPC();
// Test mode #1
if (cst.DEBUG) {
self.opts.ROOT_URL = '127.0.0.1';
if (process.env.NODE_ENV == 'test')
self.opts.ROOT_PORT = 3400;
else
self.opts.ROOT_PORT = 3000;
}
else {
self.opts.ROOT_URL = cst.KEYMETRICS_ROOT_URL;
}
if (Conf.getSync('pm2:passwd'))
global._pm2_password_protected = true;
// Test mode #2
if (process.env.NODE_ENV == 'local_test') {
self.opts.DAEMON_ACTIVE = true;
self.opts.ipm2 = self.connectToPM2();
PushInteractor.start({
url : 'http://127.0.0.1:4321',
conf : self.opts
});
ReverseInteractor.start({
url : 'http://127.0.0.1:4322',
conf : self.opts
});
if (process.send)
process.send({
success : true,
debug : true
});
return false;
}
Daemon.welcome(function(err, km_data) {
if (err) {
if (process.send)
process.send({
error : true,
msg : err.stack || err
});
console.log(err.stack || err);
return Daemon.exit();
}
if (km_data.disabled == true) {
console.error('Interactor disabled');
return Daemon.exit();
}
if (km_data.pending == true) {
console.error('Interactor pending');
return Daemon.exit();
}
if (km_data.active == true) {
self.opts.DAEMON_ACTIVE = true;
self.opts.ipm2 = self.connectToPM2();
WatchDog.start({
conf : self.opts
});
PushInteractor.start({
url : km_data.endpoints.push,
conf : self.opts
});
if (self.opts.REVERSE_INTERACT == true) {
ReverseInteractor.start({
url : km_data.endpoints.reverse,
conf : self.opts
});
}
Daemon.refreshWorker();
Daemon.pingKeepAlive();
}
else {
console.log('Nothing to do, exiting');
Daemon.exit();
}
return false;
});
}
};
/**
* MAIN
*/
if (require.main === module) {
console.log(chalk.cyan.bold('[Keymetrics.io]') + ' Launching agent');
process.title = 'PM2: KM Agent (' + process.env.PM2_HOME + ')';
Utility.overrideConsole();
Daemon.protectedStart();
}

View File

@ -1,124 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
/**
* @file Filter process and system data to be sent to server
* @author Alexandre Strzelewicz <strzelewicz.alexandre@gmail.com>
* @project Interface
*/
var os = require('os');
var cpu_info = {
number : 0,
info : 'no-data'
};
try {
cpu_info = {
number : os.cpus().length,
info : os.cpus()[0].model
};
} catch(e) {
}
var SERVER_META = {
totalMem : os.totalmem(),
hostname : os.hostname(),
type : os.type(),
platform : os.platform(),
arch : os.arch()
};
var Filter = {};
Filter.getProcessID = function(machine_name, name, id) {
return machine_name + ':' + name + ':' + id;
};
Filter.machineSnapshot = function(processes, conf) {
if (!processes) return null;
var filter_procs = [];
processes.forEach(function(proc) {
if (proc.pm2_env.pm_id.toString().indexOf('_old_') == -1)
filter_procs.push({
pid : proc.pid,
name : proc.pm2_env.name,
interpreter : proc.pm2_env.exec_interpreter,
restart_time : proc.pm2_env.restart_time,
created_at : proc.pm2_env.created_at,
exec_mode : proc.pm2_env.exec_mode,
watching : proc.pm2_env.watch,
pm_uptime : proc.pm2_env.pm_uptime,
status : proc.pm2_env.status,
pm_id : proc.pm2_env.pm_id,
cpu : Math.floor(proc.monit.cpu) || 0,
memory : Math.floor(proc.monit.memory) || 0,
versioning : proc.pm2_env.versioning || null,
node_env : proc.pm2_env.NODE_ENV || null,
axm_actions : proc.pm2_env.axm_actions || [],
axm_monitor : proc.pm2_env.axm_monitor || {},
axm_options : proc.pm2_env.axm_options || {},
axm_dynamic : proc.pm2_env.dynamic || {}
});
});
var node_version = process.version || '';
if (node_version != '') {
if (node_version.indexOf('v1.') === 0 || node_version.indexOf('v2.') === 0 || node_version.indexOf('v3.') === 0)
node_version = 'iojs ' + node_version;
}
var username = process.env.SUDO_USER || process.env.C9_USER || process.env.LOGNAME ||
process.env.USER || process.env.LNAME || process.env.USERNAME;
return {
process : filter_procs,
server : {
loadavg : os.loadavg(),
total_mem : SERVER_META.totalMem,
free_mem : os.freemem(),
cpu : cpu_info,
hostname : SERVER_META.hostname,
uptime : os.uptime(),
type : SERVER_META.type,
platform : SERVER_META.platform,
arch : SERVER_META.arch,
user : username,
interaction : conf.REVERSE_INTERACT,
pm2_version : conf.PM2_VERSION,
node_version : node_version
}
};
};
Filter.monitoring = function(processes, conf) {
if (!processes) return null;
var filter_procs = {};
processes.forEach(function(proc) {
filter_procs[Filter.getProcessID(conf.MACHINE_NAME, proc.pm2_env.name,proc.pm2_env.pm_id)] = [
Math.floor(proc.monit.cpu),
Math.floor(proc.monit.memory)
];
});
return {
loadavg : os.loadavg(),
total_mem : SERVER_META.totalMem,
free_mem : os.freemem(),
processes : filter_procs
};
};
module.exports = Filter;

View File

@ -1,86 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var http = require('http');
var https = require('https');
var url = require('url')
var debug = require('debug')('interface:http');
var HttpRequest = module.exports = {};
HttpRequest.post = function(opts, cb) {
if (!(opts.data && opts.url)) {
return cb({
msg: 'missing parameters',
port: opts.port,
data: opts.data,
url: opts.url
})
}
if (!opts.port) {
var parsed = url.parse(opts.url)
if (parsed.hostname && parsed.port) {
opts.port = parseInt(parsed.port)
opts.url = parsed.hostname
} else {
opts.port = 443
}
}
var options = {
hostname: opts.url,
path: '/api/node/verifyPM2',
method: 'POST',
port: opts.port,
rejectUnauthorized: false,
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(opts.data))
}
}
var client = (opts.port === 443) ? https : http;
var req = client.request(options, function(res){
var dt = '';
res.on('data', function (chunk) {
dt += chunk;
});
res.on('end',function(){
try {
cb(null, JSON.parse(dt));
} catch(e) {
cb(e);
}
});
res.on('error', function(e){
cb(e);
});
});
req.on('socket', function (socket) {
/**
* Configure request timeout
*/
socket.setTimeout(7000);
socket.on('timeout', function() {
debug('Connection timeout when retrieveing PM2 metadata', options);
req.abort();
});
});
req.on('error', function(e) {
cb(e);
});
req.write(JSON.stringify(opts.data));
req.end();
};

View File

@ -1,535 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
'use strict';
var debug = require('debug')('pm2:interface:daemon');
var fs = require('fs');
var path = require('path');
var util = require('util');
var rpc = require('pm2-axon-rpc');
var axon = require('pm2-axon');
var chalk = require('chalk');
var os = require('os');
var cst = require('../../constants.js');
var Common = require('../Common');
var json5 = require('../tools/json5.js');
var UX = require('../API/CliUx.js');
var InteractorDaemonizer = module.exports = {};
InteractorDaemonizer.rpc = {};
/**
* Description
* @method ping
* @param {} cb
* @return
*/
InteractorDaemonizer.ping = function(conf, cb) {
var req = axon.socket('req');
var client = new rpc.Client(req);
debug('[PING INTERACTOR] Trying to connect to Interactor daemon');
client.sock.once('reconnect attempt', function() {
client.sock.close();
debug('Interactor Daemon not launched');
return cb(false);
});
client.sock.once('connect', function() {
client.sock.once('close', function() {
return cb(true);
});
client.sock.close();
debug('Interactor Daemon alive');
});
client.sock.once('error', function(e) {
if (e.code == 'EACCES') {
fs.stat(conf.INTERACTOR_RPC_PORT, function(e, stats) {
if (stats.uid === 0) {
console.error(conf.PREFIX_MSG_ERR + 'Permission denied, activate current user:');
console.log(chalk.bold('$sudo chown ' + process.env.USER + ':' + process.env.USER + ' ' + conf.INTERACTOR_RPC_PORT));
return process.exit(1);
}
});
}
});
req.connect(conf.INTERACTOR_RPC_PORT);
};
InteractorDaemonizer.killInteractorDaemon = function(conf, cb) {
process.env.PM2_INTERACTOR_PROCESSING = true;
debug('Killing interactor #1 ping');
InteractorDaemonizer.ping(conf, function(online) {
debug('Interactor online', online);
if (!online) {
if (!cb) Common.printError('Interactor not launched');
return cb(new Error('Interactor not launched'));
}
InteractorDaemonizer.launchRPC(conf, function(err, data) {
if (err) {
setTimeout(function() {
InteractorDaemonizer.disconnectRPC(cb);
}, 100);
return false;
}
InteractorDaemonizer.rpc.kill(function(err) {
if (err) Common.printError(err);
setTimeout(function() {
InteractorDaemonizer.disconnectRPC(cb);
}, 100);
});
return false;
});
return false;
});
};
/**
* Description
* @method launchRPC
* @param {} cb
* @return
*/
InteractorDaemonizer.launchRPC = function(conf, cb) {
var self = this;
var req = axon.socket('req');
this.client = new rpc.Client(req);
debug('Generating methods');
/**
* Description
* @method generateMethods
* @param {} cb
* @return
*/
var generateMethods = function(cb) {
self.client.methods(function(err, methods) {
Object.keys(methods).forEach(function(key) {
var method_signature = methods[key];
debug('+-- Creating %s method', method_signature.name);
(function(name) {
/**
* Description
* @method name
* @return
*/
self.rpc[name] = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(name);
self.client.call.apply(self.client, args);
};
})(method_signature.name);
});
return cb();
});
};
this.client.sock.once('reconnect attempt', function(e) {
self.client.sock.removeAllListeners();
return cb({success:false, msg:'reconnect attempt'});
});
this.client.sock.once('error', function(e) {
console.error('Error in error catch all on Interactor');
console.error(e.stack || e);
});
this.client.sock.once('connect', function() {
self.client.sock.removeAllListeners();
generateMethods(function() {
debug('Methods generated');
cb(null, {success:true});
});
});
this.client_sock = req.connect(conf.INTERACTOR_RPC_PORT);
};
/**
* Description
* @method launchOrAttach
* @param {} secret_key
* @param {} public_key
* @param {} machine_name
* @param {} cb
* @return
*/
function launchOrAttach(conf, infos, cb) {
InteractorDaemonizer.ping(conf, function(online) {
if (online) {
debug('Interactor online, restarting it...');
InteractorDaemonizer.launchRPC(conf, function() {
InteractorDaemonizer.rpc.kill(function(err) {
daemonize(conf, infos, function(err, msg, proc) {
return cb(err, msg, proc);
});
});
});
}
else {
debug('Interactor offline, launching it...');
daemonize(conf, infos, function(err, msg, proc) {
return cb(err, msg, proc);
});
}
return false;
});
};
/**
* Description
* @method daemonize
* @param {} secret_key
* @param {} public_key
* @param {} machine_name
* @param {} cb
* @return
*/
var daemonize = function(conf, infos, cb) {
var InteractorJS = path.resolve(path.dirname(module.filename), 'Daemon.js');
var out = null;
var err = null;
if (process.env.TRAVIS || process.env.NODE_ENV == 'local_test') {
// Redirect PM2 internal err and out
// to STDERR STDOUT when running with Travis
out = 1;
err = 2;
}
else {
out = fs.openSync(conf.INTERACTOR_LOG_FILE_PATH, 'a');
err = fs.openSync(conf.INTERACTOR_LOG_FILE_PATH, 'a');
}
var child = require('child_process').spawn('node', [InteractorJS], {
silent : false,
detached : true,
cwd : process.cwd(),
env : util._extend({
PM2_HOME : conf.PM2_HOME,
PM2_MACHINE_NAME : infos.machine_name,
PM2_SECRET_KEY : infos.secret_key,
PM2_PUBLIC_KEY : infos.public_key,
PM2_REVERSE_INTERACT : infos.reverse_interact,
KEYMETRICS_NODE : infos.info_node
}, process.env),
stdio : ['ipc', out, err]
});
console.log('[KM] Connecting');
fs.writeFileSync(conf.INTERACTOR_PID_PATH, child.pid);
function onError(msg) {
debug('Error when launching Interactor, please check the agent logs');
return cb(msg);
}
child.once('error', onError);
child.unref();
var t = setTimeout(function() {
Common.printOut(cst.PREFIX_MSG_WARNING + ' Not managed to connect to Keymetrics, retrying in background. (check %s)', cst.INTERACTOR_LOG_FILE_PATH);
child.removeAllListeners('message');
child.removeAllListeners('error');
child.disconnect();
return cb(null, {}, child);
}, 7000);
child.once('message', function(msg) {
clearTimeout(t);
debug('Interactor daemon launched', msg);
if (msg.debug) {
return cb(null, msg, child);
}
child.removeAllListeners('error');
child.disconnect();
/*****************
* Error messages
*/
if (msg.error == true) {
console.log(chalk.red('[Keymetrics.io][ERROR]'), msg.msg);
console.log(chalk.cyan('[Keymetrics.io]') + ' Contact support contact@keymetrics.io and send us the error message');
return cb(msg);
}
if (msg.km_data.disabled == true) {
console.log(chalk.cyan('[Keymetrics.io]') + ' Server DISABLED BY ADMINISTRATION contact support contact@keymetrics.io with reference to your public and secret keys)');
return cb(msg);
}
if (msg.km_data.error == true) {
console.log(chalk.red('[Keymetrics.io][ERROR]') + ' ' + msg.km_data.msg + ' (Public: %s) (Secret: %s) (Machine name: %s)', msg.public_key, msg.secret_key, msg.machine_name);
return cb(msg);
}
else if (msg.km_data.active == false && msg.km_data.pending == true) {
console.log(chalk.red('[Keymetrics.io]') + ' ' + chalk.bold.red('Agent PENDING') + ' - Web Access: https://app.keymetrics.io/');
console.log(chalk.red('[Keymetrics.io]') + ' You must upgrade your bucket in order to monitor more servers.');
return cb(msg);
}
if (msg.km_data.active == true) {
console.log(chalk.green.bold('[Monitoring Enabled]') + ' Dashboard access: https://app.keymetrics.io/#/r/%s', msg.public_key);
return cb(null, msg, child);
}
return cb(null, msg, child);
});
};
InteractorDaemonizer.update = function(conf, cb) {
InteractorDaemonizer.ping(conf, function(online) {
if (!online) {
Common.printError('Interactor not launched');
return cb(new Error('Interactor not launched'));
}
InteractorDaemonizer.launchRPC(conf, function() {
InteractorDaemonizer.rpc.kill(function(err) {
if (err) {
Common.printError(err);
return cb(new Error(err));
}
Common.printOut('Interactor successfully killed');
setTimeout(function() {
InteractorDaemonizer.launchAndInteract(conf, {}, function() {
return cb(null, {msg : 'Daemon launched'});
});
}, 500);
});
});
return false;
});
};
/**
* Get/Update/Merge agent configuration
* @param {object} _infos
*/
InteractorDaemonizer.getOrSetConf = function(conf, infos, cb) {
var reverse_interact = true;
var version_management_active = true;
var version_management_password = null;
var secret_key;
var public_key;
var machine_name;
var info_node;
var new_connection = false;
// 1# Load configuration file
try {
var interaction_conf = json5.parse(fs.readFileSync(conf.INTERACTION_CONF));
public_key = interaction_conf.public_key;
machine_name = interaction_conf.machine_name;
secret_key = interaction_conf.secret_key;
info_node = interaction_conf.info_node;
reverse_interact = interaction_conf.reverse_interact || true;
if (interaction_conf.version_management) {
version_management_password = interaction_conf.version_management.password || version_management_password;
version_management_active = interaction_conf.version_management.active || version_management_active;
}
} catch (e) {
debug('Interaction file does not exists');
}
// 2# Override with passed informations
if (infos) {
if (infos.secret_key)
secret_key = infos.secret_key;
if (infos.public_key)
public_key = infos.public_key;
if (infos.machine_name)
machine_name = infos.machine_name;
if (infos.info_node)
info_node = infos.info_node;
new_connection = true;
}
// 3# Override with environment variables (highest-priority conf)
if (process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET)
secret_key = process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET;
if (process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC)
public_key = process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC;
if (new_connection && info_node == null)
info_node = process.env.KEYMETRICS_NODE || cst.KEYMETRICS_ROOT_URL;
if (!info_node)
info_node = cst.KEYMETRICS_ROOT_URL;
if (!secret_key)
return cb(new Error('secret key is not defined'));
if (!public_key)
return cb(new Error('public key is not defined'));
if (!machine_name)
machine_name = os.hostname() + '-' + require('crypto').randomBytes(4).toString('hex');
/**
* Write new data to configuration file
*/
try {
var new_interaction_conf = {
secret_key : secret_key,
public_key : public_key,
machine_name : machine_name,
reverse_interact : reverse_interact,
info_node : info_node,
version_management : {
active : version_management_active,
password : version_management_password
}
};
fs.writeFileSync(conf.INTERACTION_CONF, json5.stringify(new_interaction_conf, null, 4));
} catch(e) {
console.error('Error when writting configuration file %s', conf.INTERACTION_CONF);
return cb(e);
}
// Don't block the event loop
process.nextTick(function() {
cb(null, new_interaction_conf);
});
};
InteractorDaemonizer.disconnectRPC = function(cb) {
if (!InteractorDaemonizer.client_sock ||
!InteractorDaemonizer.client_sock.close)
return cb(null, {
success : false,
msg : 'RPC connection to Interactor Daemon is not launched'
});
if (InteractorDaemonizer.client_sock.connected === false ||
InteractorDaemonizer.client_sock.closing === true) {
return cb(null, {
success : false,
msg : 'RPC closed'
});
}
try {
var timer;
debug('Closing RPC INTERACTOR');
InteractorDaemonizer.client_sock.once('close', function() {
debug('RPC INTERACTOR cleanly closed');
clearTimeout(timer);
return cb ? cb(null, {success:true}) : false;
});
timer = setTimeout(function() {
if (InteractorDaemonizer.client_sock.destroy)
InteractorDaemonizer.client_sock.destroy();
return cb ? cb(null, {success:true}) : false;
}, 200);
InteractorDaemonizer.client_sock.close();
} catch(e) {
debug('Error while closing RPC INTERACTOR', e.stack || e);
return cb ? cb(e.stack || e) : false;
}
return false;
};
InteractorDaemonizer.rescueStart = function(conf, cb) {
InteractorDaemonizer.getOrSetConf(conf, null, function(err, infos) {
if (err || !infos) {
return cb(err);
}
console.log(chalk.cyan('[Keymetrics.io]') + ' Using (Public key: %s) (Private key: %s)', infos.public_key, infos.secret_key);
daemonize(conf, infos, function(err, msg, proc) {
return cb(err, msg, proc);
});
});
};
InteractorDaemonizer.launchAndInteract = function(conf, opts, cb) {
// For Watchdog
if (process.env.PM2_AGENT_ONLINE) {
return process.nextTick(cb);
}
process.env.PM2_INTERACTOR_PROCESSING = true;
this.getOrSetConf(conf, opts, function(err, data) {
if (err || !data) {
return cb(err);
}
//console.log(chalk.cyan('[Keymetrics.io]') + ' Using (Public key: %s) (Private key: %s)', data.public_key, data.secret_key);
launchOrAttach(conf, data, function(err, msg, proc) {
if (err)
return cb(err);
return cb(null, msg, proc);
});
return false;
});
};
/**
* Description
* @method getInteractInfo
* @param {} cb
* @return
*/
InteractorDaemonizer.getInteractInfo = function(conf, cb) {
debug('Getting interaction info');
if (process.env.PM2_NO_INTERACTION) return;
InteractorDaemonizer.ping(conf, function(online) {
if (!online) {
return cb(new Error('Interactor is offline'));
}
InteractorDaemonizer.launchRPC(conf, function() {
InteractorDaemonizer.rpc.getInfos(function(err, infos) {
if (err)
return cb(err);
// Avoid general CLI to interfere with Keymetrics CLI commands
if (process.env.PM2_INTERACTOR_PROCESSING)
return cb(null, infos);
InteractorDaemonizer.disconnectRPC(function() {
return cb(null, infos);
});
return false;
});
});
return false;
});
};

View File

@ -1,71 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var crypto = require('crypto');
var saltChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var saltCharsCount = saltChars.length;
function generateSalt(len) {
if (typeof len != 'number' || len <= 0 || len !== parseInt(len, 10)) throw new Error('Invalid salt length');
if (crypto.randomBytes) {
return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').substring(0, len);
} else {
for (var i = 0, salt = ''; i < len; i++) {
salt += saltChars.charAt(Math.floor(Math.random() * saltCharsCount));
}
return salt;
}
}
function generateHash(algorithm, salt, password, iterations) {
iterations = iterations || 1;
try {
var hash = password;
for(var i=0; i<iterations; ++i) {
hash = crypto.createHmac(algorithm, salt).update(hash).digest('hex');
}
return algorithm + '$' + salt + '$' + iterations + '$' + hash;
} catch (e) {
throw new Error('Invalid message digest algorithm');
}
}
function makeBackwardCompatible(hashedPassword) {
var parts = hashedPassword.split('$');
if(parts.length === 3) {
parts.splice(2,0,1);
hashedPassword = parts.join("$");
}
return hashedPassword;
}
module.exports.generate = function(password, options) {
if (typeof password != 'string') throw new Error('Invalid password');
options || (options = {});
options.algorithm || (options.algorithm = 'sha1');
options.saltLength || options.saltLength == 0 || (options.saltLength = 8);
options.iterations || (options.iterations = 1);
var salt = generateSalt(options.saltLength);
return generateHash(options.algorithm, salt, password, options.iterations);
};
module.exports.verify = function(password, hashedPassword) {
if (!password || !hashedPassword) return false;
hashedPassword = makeBackwardCompatible(hashedPassword);
var parts = hashedPassword.split('$');
if (parts.length != 4) return false;
try {
return generateHash(parts[0], parts[1], password, parts[2]) == hashedPassword;
} catch (e) {}
return false;
};
module.exports.isHashed = function(password) {
if (!password) return false;
return password.split('$').length == 4;
}

View File

@ -1,380 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var axon = require('pm2-axon');
var os = require('os');
var debug = require('debug')('interface:push-interactor');
var debugInfo = require('debug')('interface:push:delay');
var util = require('util');
var Url = require('url');
var fs = require('fs');
var path = require('path');
var pkg = require('../../package.json');
var cst = require('../../constants.js');
var Filter = require('./Filter.js');
var Cipher = require('./Cipher.js');
var Utility = require('../Utility.js');
var InteractorUtility = require('./Utility.js');
var Aggregator = require('./TransactionAggregator.js');
var LOGS_BUFFER = {};
/**
* Instanciate a new axon connection
*/
function setupConnection(host, port) {
var that = this;
this._setup = function(host, port) {
console.log('[PUSH] Connecting %s:%s', host, port || cst.REMOTE_PORT_TCP);
var client = this.client = axon.socket('pub');
if (port) port = parseInt(port)
if (port === 41624) port = 80
this.host = host;
client.on('connect', function() {
console.log('[PUSH] Connected');
});
client.on('error', function(e) {
console.log('[PUSH] Client got error', e.message);
});
client.on('close', function(e) {
console.log('[PUSH] Connection closed');
});
client.on('reconnect attempt', function(e) {
console.log('[PUSH] Reconnecting');
});
client.connect(port || cst.REMOTE_PORT_TCP, host);
};
this.destroy = function() {
this.client.close();
this.client.removeAllListeners();
};
this.reconnect = function() {
this.destroy();
this._setup(this.host);
};
this._setup(host, port);
return this;
};
var PushInteractor = module.exports = {
/**
* Connect to target host or reconnect if null is passed
* the host param must be formated like (http://HOST:PORT)
*/
connectRemote: function (hostname, port) {
if (this.socket) this.socket.destroy()
if (hostname) {
var parsed = Url.parse(hostname)
this.socket = setupConnection(parsed.hostname, port || parsed.port)
} else if (this.socket && this.socket.host) {
this.socket = setupConnection(this.socket.host)
} else {
return console.error('NO HOST DEFINED')
}
},
/**
* Start the PushInteractor Singleton
*/
start : function(p) {
if (!p.url)
throw new Error('missing endpoint url');
if (!p.conf || !p.conf.ipm2)
throw new Error('ipm2 is not initialized');
var self = this;
this.monitored_processes = {};
this.conf = p.conf;
this.ipm2 = p.conf.ipm2;
this.send_buffer = [];
this._reconnect_counter = 0;
this.port = null
if (process.env.PM2_DEBUG)
this.port = 3900;
if (process.env.NODE_ENV == 'local_test')
this.port = 8080;
this.resetPacket();
this.connectRemote(p.url, this.port);
this.ipm2.on('ready', function() {
console.log('[PUSH] PM2 interface ready, listening to PM2');
self.listenToPM2Events();
});
self.startPoolingWorker();
self.cache = new InteractorUtility.Cache({
miss: function (key) {
try {
var content = fs.readFileSync(path.resolve(key));
return content.toString().split(/\r?\n/);
} catch (err) {
debug('Error while trying to get file from FS : %s', err.message || err)
return undefined;
}
},
ttl: 60 * 30
});
self.stackParser = new InteractorUtility.StackTraceParser({ cache: self.cache, context: cst.CONTEXT_ON_ERROR });
self.aggregator = new Aggregator(self);
self.aggregator.init();
},
/**
* Send bufferized data at regular interval
*/
startPoolingWorker : function() {
var self = this;
setInterval(function() {
debug('[PUSH] +---- Pooling: sending data ----+');
PushInteractor.sendData();
}, cst.SEND_INTERVAL);
},
/**
* Send profiling file asynchronously
*/
sendFile : function(packet) {
var self = this;
var file = JSON.parse(JSON.stringify(packet.data.return.dump_file));
var meta = {
pm_id : packet.process.pm_id,
name : packet.process.name,
server_name : PushInteractor.conf.MACHINE_NAME,
public_key : self.conf.PUBLIC_KEY
};
if (packet.data.return.heapdump === true)
meta.heapdump = true;
if (packet.data.return.cpuprofile === true)
meta.cpuprofile = true;
fs.readFile(file, function(err, data) {
if (err) return console.error(err.stack || err);
fs.unlink(file, function(e) { if (e) console.error(e.stack || e);});
return self.socket.client.send(JSON.stringify(meta), data);
});
},
listenToPM2Events : function() {
var self = this;
this.ipm2.bus.on('*', function(event, packet) {
if (event == 'axm:action') return false;
// Drop transitional state processes (_old_*)
if (packet &&
packet.process &&
packet.process.pm_id &&
(typeof(packet.process.pm_id) == 'string' &&
packet.process.pm_id.indexOf('_old') > -1))
return false;
if (Object.keys(self.monitored_processes).length > 0 &&
!self.monitored_processes[packet.process.pm_id])
return false;
// keep log in a buffer
if (event.match(/^log:/)) {
if (!LOGS_BUFFER[packet.process.name]) {
LOGS_BUFFER[packet.process.name] = [];
}
// push the log data
LOGS_BUFFER[packet.process.name].push(packet.data);
// delete the last one if too long
if (LOGS_BUFFER[packet.process.name].length >= cst.LOGS_BUFFER_SIZE) {
LOGS_BUFFER[packet.process.name].pop();
}
// don't send if not asked
if (!global._logs) return false;
}
// attach additional info on exception
if (event === 'process:exception') {
packet.data.last_logs = LOGS_BUFFER[packet.process.name];
packet.data = self.stackParser.attachContext(packet.data);
}
/**
* This is a heapdump action
*/
if (event == 'axm:reply' && packet.data && packet.data.return && (packet.data.return.heapdump || packet.data.return.cpuprofile)) {
PushInteractor.sendFile(packet);
return false;
}
if (event == 'human:event') {
packet.name = packet.data.__name + '';
delete packet.data.__name;
}
if (!packet.process)
return console.error('No process field [%s]', event);
/**
* Process specific messages
* -- Reformat raw output of pm2-interface
*/
packet.process = {
pm_id : packet.process.pm_id,
name : packet.process.name,
rev : packet.process.rev || ((packet.process.versioning && packet.process.versioning.revision) ? packet.process.versioning.revision : null),
server: PushInteractor.conf.MACHINE_NAME
};
// agregate transaction data before sending them
if (event.indexOf('axm:trace') > -1)
return self.aggregator.aggregate(packet);
if (event.match(/^log:/)) {
packet.log_type = event.split(':')[1];
event = 'logs';
}
return PushInteractor.bufferData(event, packet);
});
},
resetPacket : function() {
var self = this;
this._packet = {
'server_name' : self.conf.MACHINE_NAME,
'status' : {},
'monitoring' : {}
};
},
bufferData : function(event, packet) {
var self = this;
var logs_limit_size = 1024 * 50;
// if (Object.keys(self._packet).indexOf(event) == -1) {
// return console.error('SKIP unknown field name [%s]', event);
// }
debug('Buffering one more event %s', event);
if (!(event in self._packet))
self._packet[event] = [];
if (packet.process && !packet.server) {
if (event === 'logs'
&& (JSON.stringify(self._packet[event]).length > logs_limit_size
|| self._packet[event].length > 100))
return console.error('Logs packet larger than 50KB limit');
self._packet[event].push(packet);
}
else {
console.error('Got packet without any process');
}
return false;
},
preparePacket : function(cb) {
var self = this;
this.ipm2.rpc.getMonitorData({}, function(err, processes) {
if (!processes)
return console.error('Cant access to getMonitorData RPC PM2 method');
processes = processes.filter(function (proc) {
return proc.pm2_env._km_monitored !== false;
});
var ret = null;
if ((ret = Filter.monitoring(processes, PushInteractor.conf))) {
self._packet['monitoring'] = ret;
}
if ((ret = Filter.machineSnapshot(processes, PushInteractor.conf))) {
self._packet['status'] = {
data : ret,
server_name : self.conf.MACHINE_NAME,
internal_ip : self.conf.internal_ip,
protected : global._pm2_password_protected,
rev_con : self.conf.rev_con
};
}
return cb ? cb(null, ret) : false;
});
},
/**
* Description
* @method send_data
* @return
*/
sendData : function() {
var self = this;
if (self.socket.client &&
self.socket.client.socks[0] &&
self.socket.client.socks[0].bufferSize > 290000) {
self.resetPacket();
self._reconnect_counter++;
console.log('Buffer size too high (%d), stopping buffering and sending', self.socket.client.socks[0].bufferSize);
if (self._reconnect_counter > 20) {
console.log('[PUSH] Forcing reconnection');
self._reconnect_counter = 0;
self.socket.reconnect();
}
return false;
}
this.preparePacket(function() {
var data = {};
if (process.env.NODE_ENV &&
(process.env.NODE_ENV == 'test' || process.env.NODE_ENV == 'local_test')) {
data = {
public_key : PushInteractor.conf.PUBLIC_KEY,
sent_at : Utility.getDate(),
data : self._packet
};
}
else {
var cipheredData = Cipher.cipherMessage(JSON.stringify(self._packet),
PushInteractor.conf.SECRET_KEY);
data = {
public_key : self.conf.PUBLIC_KEY,
sent_at : Utility.getDate(),
data : cipheredData
};
}
var str = JSON.stringify(data);
var t1 = new Date();
self.resetPacket();
if (!self.socket) return false;
self.socket.client.sendv2(str, function() {
var duration_sec = (new Date() - t1) / 1000;
debugInfo('Time to flush data %ds (buffer size %d)', duration_sec);
if (duration_sec > 1)
console.info('[WARN] Time to send data over TCP took %dseconds!', duration_sec);
data = null;
str = null;
});
});
}
};

View File

@ -1,92 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var debug = require('debug')('interface:driver');
var Cipher = require('../Cipher.js');
var CustomActions = module.exports = {
/**
* Method to trigger custom actions (axm actions)
*/
axmCustomActions : function() {
var self = this;
this.socket.data('trigger:action', function(raw_msg) {
var msg = {};
if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' ||
process.env.NODE_ENV == 'local_test'))
msg = raw_msg;
else
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
if (!msg) return console.error('Error while receiving message! #axmCustomActions');
console.log('New remote action %s triggered for process %s', msg.action_name, msg.process_id);
self.pm2_instance.msgProcess({
id : msg.process_id,
msg : msg.action_name,
opts: msg.opts || null
}, function(err, data) {
if (err) {
return self.socket.send('trigger:action:failure', {
success : false,
err : err.message,
id : msg.process_id,
action_name : msg.action_name
});
}
console.log('[REVERSE INTERACTOR] Message received from AXM for proc_id : %s and action name %s',
msg.process_id, msg.action_name);
return self.socket.send('trigger:action:success', {
success : true,
id : msg.process_id,
action_name : msg.action_name
});
});
});
this.socket.data('trigger:scoped_action', function(raw_msg) {
var msg = {};
if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' ||
process.env.NODE_ENV == 'local_test'))
msg = raw_msg;
else
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
if (!msg) return console.error('Error while receiving message! #axmCustomActions');
console.log('New SCOPED action %s triggered for process %s', msg.action_name, msg.process.pm_id);
self.pm2_instance.msgProcess({
id : msg.process.pm_id,
action_name : msg.action_name,
msg : msg.action_name,
opts : msg.options || {},
uuid : msg.uuid
}, function(err, data) {
if (err) {
return self.socket.send('trigger:action:failure', {
success : false,
err : err.message,
id : msg.process.pm_id,
action_name : msg.action_name
});
}
console.log('[REVERSE INTERACTOR] Message received from AXM for proc_id : %s and action name %s',
msg.process_id, msg.action_name);
return self.socket.send('trigger:action:success', {
success : true,
id : msg.process.pm_id,
action_name : msg.action_name
});
});
});
}
};

View File

@ -1,345 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var debug = require('debug')('interface:driver');
var Url = require('url');
var Cipher = require('../Cipher.js');
var PushInteractor = require('../PushInteractor');
var Conf = require('../../Configuration.js');
var Password = require('../Password.js');
/**
* Allowed remote PM2 methods
* with options
* - password_required : force to pass a password in parameter
* - password_optional : if a password is set, force it
* - lock : enable the locking system (block parallel commands)
*/
var PM2_REMOTE_METHOD_ALLOWED = {
'restart' : {},
'reload' : {},
'gracefulReload' : {},
'reset' : {},
'scale' : {},
'install' : { password_required : true },
'uninstall' : { password_required : true },
'stop' : { password_required : true },
'delete' : { password_required : true },
'set' : {},
'multiset' : {},
'deepUpdate' : { password_required : true },
'pullAndRestart' : { password_optional : true },
'forward' : { password_optional : true },
'backward' : { password_optional : true },
'startLogging' : {},
'stopLogging' : {},
'resetTransactionCache': {},
'resetFileCache': {},
// This is just for testing purproses
'ping' : { password_required : true }
};
var Pm2Actions = module.exports = {
/**
* Methods to trigger PM2 actions from remote
*/
pm2Actions : function() {
var self = this;
function executionBox(msg, cb) {
/**
* Exemple
* msg = {
* method_name : 'restart',
* parameters : {}
* }
*/
console.log('PM2 action from remote triggered "pm2 %s %j"',
msg.method_name,
msg.parameters);
var method_name = JSON.parse(JSON.stringify(msg.method_name));
var parameters = '';
try {
parameters = JSON.parse(JSON.stringify(msg.parameters));
}
catch(e) {
console.error(e.stack || e);
parameters = msg.parameters;
}
if (!method_name) {
console.error('no method name');
return cb(new Error('no method name defined'));
}
if (!PM2_REMOTE_METHOD_ALLOWED[method_name]) {
console.error('method %s not allowed', method_name);
return cb(new Error('method ' + method_name + ' not allowed'));
}
if (method_name === 'startLogging') {
global._logs = true;
// Stop streaming logs automatically after timeout
setTimeout(function() {
global._logs = false;
}, 120000);
return cb(null, 'Log streaming enabled');
} else if (method_name === 'stopLogging') {
global._logs = false;
return cb(null, 'Log streaming disabled');
} else if (method_name === 'resetTransactionCache') {
PushInteractor.aggregator.clearData();
return cb(null, 'Transaction cache has beem reset');
} else if (method_name === 'resetFileCache') {
PushInteractor.cache.reset();
return cb(null, 'File cache has beem reset');
}
self.pm2_instance.remote(method_name, parameters, cb);
return false;
}
function sendBackResult(data) {
self.socket.send('trigger:pm2:result', data);
};
this.socket.data('trigger:pm2:action', function(raw_msg) {
var d = require('domain').create();
var msg = {};
/**
* Uncipher Data
*/
if (process.env.NODE_ENV &&
(process.env.NODE_ENV == 'test' ||
process.env.NODE_ENV == 'local_test'))
msg = raw_msg;
else
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
d.on('error', function(e) {
console.error('Error caught in domain');
console.error(e.stack || e);
/**
* Send error back to
*/
sendBackResult({
ret : {
err : e,
data : null
},
meta : {
method_name : msg.method_name,
app_name : msg.parameters.name,
machine_name : self.conf.MACHINE_NAME,
public_key : self.conf.PUBLIC_KEY
}
});
});
d.run(function() {
if (!msg)
throw new Error('Wrong SECRET KEY to uncipher package');
/**
* Execute command
*/
executionBox(msg, function(err, data) {
if (err) console.error(err.stack || JSON.stringify(err));
/**
* Send back the result
*/
sendBackResult({
ret : {
err : err,
data : data || null
},
meta : {
method_name : msg.method_name,
app_name : msg.parameters.name,
machine_name : self.conf.MACHINE_NAME,
public_key : self.conf.PUBLIC_KEY
}
});
});
});
});
},
/****************************************************
*
*
* Scoped PM2 Actions with streaming and multi args
*
*
****************************************************/
pm2ScopedActions : function() {
var self = this;
this.socket.data('trigger:pm2:scoped:action', function(raw_msg) {
var msg = {};
if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' ||
process.env.NODE_ENV == 'local_test'))
msg = raw_msg;
else {
/**
* Uncipher Data
*/
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
}
if (!msg.uuid ||
!msg.action_name) {
console.error('PM2 Scoped: Parameter missing!');
return sendEvent('pm2:scoped:error', {
at : Date.now(),
out : 'Parameter missing',
msg : msg.uuid || null
});
}
sendEvent('pm2:scoped:stream', {
at : Date.now(),
out : 'Action ' + msg.action_name + ' received',
uuid : msg.uuid
});
executionBox(msg, function(err, data) {
if (err) {
console.error(err.stack || err);
return sendEvent('pm2:scoped:error', {
at : Date.now(),
out : err.stack || err,
uuid : msg.uuid
});
}
return sendEvent('pm2:scoped:end', {
at : Date.now(),
out : data,
uuid : msg.uuid
});
});
});
/**
* Compact event in Push Interactor *pipe*
*/
function sendEvent(event, data) {
var packet = {
at : Date.now(),
data : {
data : data.out,
uuid : data.uuid
}
};
if (!PushInteractor._packet[event])
PushInteractor._packet[event] = [];
PushInteractor._packet[event].push(packet);
if (process.env.NODE_ENV == 'local_test')
process.send({event : event, data : data});
};
/**
* Processing
*/
function executionBox(msg, cb) {
var action_name = msg.action_name;
var opts = msg.options;
if (!PM2_REMOTE_METHOD_ALLOWED[action_name]) {
console.error('method %s not allowed', action_name);
return cb(new Error('method ' + action_name + ' not allowed'));
}
var action_conf = PM2_REMOTE_METHOD_ALLOWED[action_name];
/**
* Password checking
*/
if (action_conf.password_required === true) {
if (!msg.password) {
console.error('Missing password in query');
return cb('Missing password in query');
}
var passwd = Conf.getSync('pm2:passwd');
if (passwd === null) {
console.error('Password at PM2 level is missing');
return cb('Password at PM2 level is missing please set password via pm2 set pm2:passwd <password>');
}
if (Password.verify(msg.password, passwd) != true) {
console.error('Password does not match');
return cb('Password does not match');
}
}
if (action_conf.lock === false)
opts.lock = false;
/**
* Fork the remote action in another process
* so we can catch the stdout/stderr and emit it
*/
var fork = require('child_process').fork;
process.env.fork_params = JSON.stringify({ action : action_name, opts : opts});
console.log('Executing: pm2 %s %s', action_name, opts.args ? opts.args.join(' ') : '');
var app = fork(__dirname + '/ScopedExecution.js', [], {
silent : true
});
app.stdout.on('data', function(dt) {
console.log(dt.toString());
sendEvent('pm2:scoped:stream', {
at : Date.now(),
out : dt.toString(),
uuid : msg.uuid
});
});
app.once('error', function(dt) {
console.error('Error got?', dt);
sendEvent('pm2:scoped:error', {
at : Date.now(),
out : 'Shit happening ' + JSON.stringify(dt),
msg : msg.uuid
});
});
app.on('message', function(dt) {
var ret = JSON.parse(dt);
if (ret.isFinished != true) return false;
console.log('Action %s finished (err= %s)',
action_name, ret.err);
return cb(ret.err, ret.dt);
});
return false;
}
}
};

View File

@ -1,34 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var pm2 = require('../../..');
var domain = require('domain');
var Utility = require('../../Utility.js');
var d = domain.create();
d.once('error', function(err) {
process.send(JSON.stringify({err: err.stack, isFinished : true}));
});
d.run(function() {
var params = JSON.parse(process.env.fork_params);
console.log('Executing: pm2 %s %s',
params.action,
params.opts.args ? params.opts.args.join(' ') : '');
pm2.connect(function() {
pm2.remoteV2(params.action, params.opts, function(err, dt) {
process.send(JSON.stringify(Utility.clone({
err: err,
dt: dt,
isFinished : true
})));
pm2.disconnect(process.exit);
});
});
});

View File

@ -1,128 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var debug = require('debug')('interface:driver');
var nssocket = require('nssocket');
var Url = require('url');
var Cipher = require('./Cipher.js');
var util = require('util');
var ReverseInteract = {
changeUrl : function(url) {
if (!this.connected) return;
console.log('[REV] Changing URL to %s', url);
this.network = Url.parse(url);
this.socket.connect(parseInt(this.network.port), this.network.hostname);
this.socket.reconnect();
},
destroy : function() {
this.socket.destroy();
},
reconnect : function() {
console.log('[REV] Reconnecting to %s', this.network.hostname);
this.socket.reconnect();
},
start : function(opts) {
var self = this;
if (!opts.url)
throw new Error('url not declared');
if (!opts.conf)
throw new Error('Conf not passed to ReverseInteractor');
this.connected = false;
this.conf = opts.conf;
this.network = Url.parse(opts.url);
this.pm2_instance = opts.conf.pm2_instance;
this.socket = new nssocket.NsSocket({
type : 'tcp4',
reconnect : true,
retryInterval : 2000,
max : Infinity,
maxListeners : 50
});
this.socket.on('error', function(e) {
self.connected = false;
console.error('[REV] %s', e.message || e);
});
this.socket.on('close', function(dt) {
self.connected = false;
});
this.socket.on('start', function() {
self.connected = true;
opts.conf.rev_con = true;
console.log('[REV] Connected to %s:%s', self.network.hostname, self.network.port);
});
console.log('[REV] Connecting to %s:%s', this.network.hostname, this.network.port);
this.socket.connect(parseInt(this.network.port), this.network.hostname);
this.onMessage();
},
/**
* Listening to remote events from Keymetrics
*/
onMessage : function() {
if (!this.socket) return console.error('Reverse interaction not initialized');
/**
* Identify this agent to Keymetrics
* via PUBLIC/PRIVATE key exchange
*/
ReverseInteract.introduceToKeymetrics();
ReverseInteract.axmCustomActions();
/**
* From Pm2Actions.js
*/
ReverseInteract.pm2Actions();
ReverseInteract.pm2ScopedActions();
return false;
},
/**
* First method called to identify this agent
*/
introduceToKeymetrics : function() {
var self = this;
this.socket.data('ask', function(raw_msg) {
if (process.env.NODE_ENV && process.env.NODE_ENV == 'test') {
// Dont cipher data in test environment
self.socket.send('ask:rep', {
success : true,
machine_name : self.conf.MACHINE_NAME,
public_key : self.conf.PUBLIC_KEY
});
} else {
var ciphered_data = Cipher.cipherMessage(JSON.stringify({
machine_name : self.conf.MACHINE_NAME
}), self.conf.SECRET_KEY);
if (!ciphered_data)
return console.error('Got wrong ciphering data %s %s', self.conf.MACHINE_NAME, self.conf.SECRET_KEY);
self.socket.send('ask:rep', {
data : ciphered_data,
public_key : self.conf.PUBLIC_KEY
});
}
return false;
});
}
};
util._extend(ReverseInteract, require('./RemoteActions/Pm2Actions.js'));
util._extend(ReverseInteract, require('./RemoteActions/CustomActions.js'));
module.exports = ReverseInteract;

View File

@ -1,684 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
/**
* Dependencies
*/
var cst = require('../../constants.js');
var log = require('debug')('pm2:aggregator');
var async = require('async');
var Utility = require('./Utility.js');
var fclone = require('fclone');
var fs = require('fs');
var path = require('path');
var Histogram = require('pmx/lib/utils/probes/Histogram.js');
var LABELS = {
"HTTP_RESPONSE_CODE_LABEL_KEY": 'http/status_code',
"HTTP_URL_LABEL_KEY": 'http/url',
"HTTP_METHOD_LABEL_KEY": 'http/method',
"HTTP_RESPONSE_SIZE_LABEL_KEY": 'http/response/size',
"STACK_TRACE_DETAILS_KEY": 'stacktrace',
"ERROR_DETAILS_NAME": 'error/name',
"ERROR_DETAILS_MESSAGE": 'error/message',
"HTTP_SOURCE_IP": 'http/source/ip',
"HTTP_PATH_LABEL_KEY": "http/path"
}
var SPANS_DB = ['redis', 'mysql', 'pg', 'mongo', 'outbound_http'];
/**
*
* # Data structure sent to interactor
*
* {
* 'process_name': {
* process : {}, // PM2 process meta data
* data : {
* routes : [ // array of all routes ordered by count
* {
* path: '/', // path of the route
* meta: {
* count: 50, // count of this route
* max: 300, // max latency of this route
* min: 50, // min latency of this route
* mean: 120 // mean latency of this route
* }
* variances: [{ // array of variance order by count
* spans : [
* ... // transactions
* ],
* count: 50, // count of this variance
* max: 300, // max latency of this variance
* min: 50, // min latency of this variance
* mean: 120 // mean latency of this variance
* }]
* }
* ],
* meta : {
* trace_count : 50, // trace number
* mean_latency : 40, // global app latency in ms
* http_meter : 30, // global app req per minutes
* db_meter : 20, // number of database transaction per min
* }
* }
* }
* }
*/
var TransactionAggregator = module.exports = function (pushInteractor) {
if (!(this instanceof TransactionAggregator)) return new TransactionAggregator(pushInteractor);
var self = this;
this.processes = {};
this.stackParser = pushInteractor.stackParser;
/**
* First method to call in real environment
* - Listen to restart event for initialization period
* - Clear aggregation on process stop
* - Launch worker to attach data to be pushed to KM
*/
this.init = function () {
// New Process Online, reset & wait a bit before processing
pushInteractor.ipm2.bus.on('process:event', function (data) {
if (data.event !== 'online' || !self.processes[data.process.name]) return false;
var rev = (data.process.versioning && data.process.versioning.revision)
? data.process.versioning.revision : null;
self.resetAggregation(data.process.name, {
rev: rev,
server: pushInteractor.conf.MACHINE_NAME
});
});
// Process getting offline, delete aggregation
pushInteractor.ipm2.bus.on('process:event', function (data) {
if (data.event !== 'stop' || !self.processes[data.process.name]) return false;
log('Deleting aggregation for %s', data.process.name);
delete self.processes[data.process.name];
});
self.launchWorker();
};
/**
* Reset aggregation for target app_name
*/
this.resetAggregation = function (app_name, meta) {
log('Reseting agg for app:%s meta:%j', app_name, meta);
if (self.processes[app_name].initialization_timeout) {
log('Reseting initialization timeout app:%s', app_name);
clearTimeout(self.processes[app_name].initialization_timeout);
clearInterval(self.processes[app_name].notifier);
self.processes[app_name].notifier = null;
}
self.processes[app_name] = initializeRouteMeta({
name: app_name,
rev: meta.rev,
server: meta.server
});
var start = Date.now();
self.processes[app_name].notifier = setInterval(function () {
var elapsed = Date.now() - start;
// failsafe
if (elapsed / 1000 > cst.AGGREGATION_DURATION) {
clearInterval(self.processes[app_name].notifier);
self.processes[app_name].notifier = null;
}
var msg = {
data: {
learning_duration: cst.AGGREGATION_DURATION,
elapsed: elapsed
},
process: self.processes[app_name].process
};
pushInteractor && pushInteractor.bufferData('axm:transaction:learning', msg);
}, 5000);
self.processes[app_name].initialization_timeout = setTimeout(function () {
log('Initialization timeout finished for app:%s', app_name);
clearInterval(self.processes[app_name].notifier);
self.processes[app_name].notifier = null;
self.processes[app_name].initialization_timeout = null;
}, cst.AGGREGATION_DURATION);
};
/**
* Clear aggregated data for all process
*/
this.clearData = function () {
var self = this;
Object.keys(this.processes).forEach(function (process) {
self.resetAggregation(process, self.processes[process].process);
});
};
/**
* Generate new entry for application
*
* @param {Object} process process meta
*/
function initializeRouteMeta (process) {
if (process.pm_id) delete process.pm_id;
return {
routes: {},
meta: {
trace_count: 0,
http_meter: new Utility.EWMA(),
db_meter: new Utility.EWMA(),
histogram: new Histogram({ measurement: 'median' }),
db_histograms: {}
},
process: process
};
}
this.getAggregation = function () {
return this.processes;
};
this.validateData = function (packet) {
if (!packet || !packet.data) {
log('Packet malformated', packet);
return false;
}
if (!packet.process) {
log('Got packet without process: %j', packet);
return false;
}
if (!packet.data.spans || !packet.data.spans[0]) {
log('Trace without spans: %s', Object.keys(packet.data));
return false;
}
if (!packet.data.spans[0].labels) {
log('Trace spans without labels: %s', Object.keys(packet.data.spans));
return false;
}
return true;
}
/**
* Main method to aggregate and compute stats for traces
*
* @param {Object} packet
* @param {Object} packet.process process metadata
* @param {Object} packet.data trace
*/
this.aggregate = function(packet) {
if (self.validateData(packet) === false) return false;
var new_trace = packet.data;
var app_name = packet.process.name;
if (!self.processes[app_name]) {
self.processes[app_name] = initializeRouteMeta(packet.process);
}
var process = self.processes[app_name];
// Get http path of current span
var path = new_trace.spans[0].labels[LABELS.HTTP_PATH_LABEL_KEY];
// Cleanup spans
self.censorSpans(new_trace.spans);
// remove spans with startTime == endTime
new_trace.spans = new_trace.spans.filter(function (span) {
return span.endTime !== span.startTime;
});
// compute duration of child spans
new_trace.spans.forEach(function (span) {
span.mean = Math.round(new Date(span.endTime) - new Date(span.startTime));
delete span.endTime;
});
// Update app meta (mean_latency, http_meter, db_meter, trace_count)
new_trace.spans.forEach(function (span) {
if (!span.name || !span.kind) return false;
if (span.kind === 'RPC_SERVER') {
process.meta.histogram.update(span.mean);
return process.meta.http_meter.update();
}
// Override outbount http queries for processing
if (span.labels && span.labels['http/method'] && span.labels['http/status_code']) {
span.labels['service'] = span.name;
span.name = 'outbound_http';
}
for (var i = 0, len = SPANS_DB.length; i < len; i++) {
if (span.name.indexOf(SPANS_DB[i]) > -1) {
process.meta.db_meter.update();
if (!process.meta.db_histograms[SPANS_DB[i]]) {
process.meta.db_histograms[SPANS_DB[i]] = new Histogram({ measurement: 'mean' });
}
process.meta.db_histograms[SPANS_DB[i]].update(span.mean);
break;
}
}
});
process.meta.trace_count++;
/**
* Handle traces aggregation
*/
if (path[0] === '/' && path !== '/') {
path = path.substr(1, path.length - 1);
}
var matched = self.matchPath(path, process.routes);
if (!matched) {
process.routes[path] = [];
self.mergeTrace(process.routes[path], new_trace, process);
} else {
self.mergeTrace(process.routes[matched], new_trace, process);
}
return self.processes;
};
/**
* Merge new trace and compute mean, min, max, count
*
* @param {Object} aggregated previous aggregated route
* @param {Object} trace
*/
this.mergeTrace = function (aggregated, trace, process) {
var self = this;
if (!aggregated || !trace) return;
// if the trace doesn't any spans stop aggregation here
if (trace.spans.length === 0) return;
// create data structure if needed
if (!aggregated.variances) aggregated.variances = [];
if (!aggregated.meta) {
aggregated.meta = {
histogram: new Histogram({ measurement: 'median' }),
meter: new Utility.EWMA()
};
}
aggregated.meta.histogram.update(trace.spans[0].mean);
aggregated.meta.meter.update();
var merge = function (variance) {
// no variance found so its a new one
if (variance == null) {
delete trace.projectId;
delete trace.traceId;
trace.histogram = new Histogram({ measurement: 'median' });
trace.histogram.update(trace.spans[0].mean);
trace.spans.forEach(function (span) {
span.histogram = new Histogram({ measurement: 'median' });
span.histogram.update(span.mean);
delete span.mean;
});
// parse strackrace
self.parseStacktrace(trace.spans);
aggregated.variances.push(trace);
} else {
// check to see if request is anormally slow, if yes send it as inquisitor
if (trace.spans[0].mean > variance.histogram.percentiles([0.95])[0.95] &&
typeof pushInteractor !== 'undefined' && !process.initialization_timeout) {
// serialize and add metadata
self.parseStacktrace(trace.spans)
var data = {
trace: fclone(trace.spans),
variance: fclone(variance.spans.map(function (span) {
return {
labels: span.labels,
kind: span.kind,
name: span.name,
startTime: span.startTime,
percentiles: {
p5: variance.histogram.percentiles([0.5])[0.5],
p95: variance.histogram.percentiles([0.95])[0.95]
}
}
})),
meta: {
value: trace.spans[0].mean,
percentiles: {
p5: variance.histogram.percentiles([0.5])[0.5],
p75: variance.histogram.percentiles([0.75])[0.75],
p95: variance.histogram.percentiles([0.95])[0.95],
p99: variance.histogram.percentiles([0.99])[0.99]
},
min: variance.histogram.getMin(),
max: variance.histogram.getMax(),
count: variance.histogram.getCount()
},
process: process.process
};
pushInteractor.bufferData('axm:transaction:outlier', data);
}
// variance found, merge spans
variance.histogram.update(trace.spans[0].mean);
// update duration of spans to be mean
self.updateSpanDuration(variance.spans, trace.spans, variance.count);
// delete stacktrace before merging
trace.spans.forEach(function (span) {
delete span.labels.stacktrace;
});
}
};
// for every variance, check spans same variance
for (var i = 0; i < aggregated.variances.length; i++) {
if (self.compareList(aggregated.variances[i].spans, trace.spans)) {
return merge(aggregated.variances[i]);
}
}
// else its a new variance
return merge(null);
};
/**
* Parkour simultaneously both spans list to update value of the first one using value of the second one
* The first should be variance already aggregated for which we want to merge the second one
* The second one is a new trace, so we need to re-compute mean/min/max time for each spans
*/
this.updateSpanDuration = function (spans, newSpans) {
for (var i = 0, len = spans.length; i < len; i++) {
if (!newSpans[i]) continue;
spans[i].histogram.update(newSpans[i].mean);
}
};
/**
* Compare two spans list by going down on each span and comparing child and attribute
*/
this.compareList = function (one, two) {
if (one.length !== two.length) return false;
for (var i = 0, len = one; i < len; i++) {
if (one[i].name !== two[i].name) return false;
if (one[i].kind !== two[i].kind) return false;
if (!one[i].labels && two[i].labels) return false;
if (one[i].labels && !two[i].labels) return false;
if (one[i].labels.length !== two[i].labels.length) return false;
}
return true;
};
/**
* Will return the route if we found an already matched route
*/
this.matchPath = function (path, routes) {
// empty route is / without the fist slash
if (!path || !routes) return false;
if (path === '/') return routes[path] ? path : null;
// remove the last slash if exist
if (path[path.length - 1] === '/') {
path = path.substr(0, path.length - 1);
}
// split to get array of segment
path = path.split('/');
// if the path has only one segment, we just need to compare the key
if (path.length === 1) return routes[path[0]] ? routes[path[0]] : null;
// check in routes already stored for match
var keys = Object.keys(routes);
for (var i = 0, len = keys.length; i < len; i++) {
var route = keys[i];
var segments = route.split('/');
if (segments.length !== path.length) continue;
for (var j = path.length - 1; j >= 0; j--) {
// different segment, try to find if new route or not
if (path[j] !== segments[j]) {
// if the aggregator already have matched that segment with a wildcard and the next segment is the same
if (self.isIdentifier(path[j]) && segments[j] === '*' && path[j - 1] === segments[j - 1]) {
return segments.join('/');
} // case a var in url match, so we continue because they must be other var in url
else if (path[j - 1] !== undefined && path[j - 1] === segments[j - 1] && self.isIdentifier(path[j]) && self.isIdentifier(segments[j])) {
segments[j] = '*';
// update routes in cache
routes[segments.join('/')] = routes[route];
delete routes[keys[i]];
return segments.join('/');
} else {
break;
}
}
// if finish to iterate over segment of path, we must be on the same route
if (j === 0) return segments.join('/');
}
}
};
this.launchWorker = function () {
setInterval(function () {
var normalized = self.prepareAggregationforShipping();
Object.keys(normalized).forEach(function (key) {
pushInteractor.bufferData('axm:transaction', normalized[key]);
});
}, cst.TRACE_FLUSH_INTERVAL);
};
/**
* Normalize aggregation
*/
this.prepareAggregationforShipping = function () {
var normalized = {};
// Iterate each applications
Object.keys(self.processes).forEach(function (app_name) {
var process = self.processes[app_name];
var routes = process.routes;
if (process.initialization_timeout) {
log('Waiting for app %s to be initialized', app_name);
return null;
}
normalized[app_name] = {
data: {
routes: [],
meta: fclone({
trace_count: process.meta.trace_count,
http_meter: Math.round(process.meta.http_meter.rate(1000) * 100) / 100,
db_meter: Math.round(process.meta.db_meter.rate(1000) * 100) / 100,
http_percentiles: {
median: process.meta.histogram.percentiles([0.5])[0.5],
p95: process.meta.histogram.percentiles([0.95])[0.95],
p99: process.meta.histogram.percentiles([0.99])[0.99]
},
db_percentiles: {}
})
},
process: process.process
};
// compute percentiles for each db spans if they exist
SPANS_DB.forEach(function (name) {
var histogram = process.meta.db_histograms[name];
if (!histogram) return;
normalized[app_name].data.meta.db_percentiles[name] = fclone(histogram.percentiles([0.5])[0.5]);
});
Object.keys(routes).forEach(function (path) {
var data = routes[path];
// hard check for invalid data
if (!data.variances || data.variances.length === 0) return;
// get top 5 variances of the same route
var variances = data.variances.sort(function (a, b) {
return b.count - a.count;
}).slice(0, 5);
// create a copy without reference to stored one
var routeCopy = {
path: path === '/' ? '/' : '/' + path,
meta: fclone({
min: data.meta.histogram.getMin(),
max: data.meta.histogram.getMax(),
count: data.meta.histogram.getCount(),
meter: Math.round(data.meta.meter.rate(1000) * 100) / 100,
median: data.meta.histogram.percentiles([0.5])[0.5],
p95: data.meta.histogram.percentiles([0.95])[0.95]
}),
variances: []
};
variances.forEach(function (variance) {
// hard check for invalid data
if (!variance.spans || variance.spans.length === 0) return;
// deep copy of variances data
var tmp = fclone({
spans: [],
count: variance.histogram.getCount(),
min: variance.histogram.getMin(),
max: variance.histogram.getMax(),
median: variance.histogram.percentiles([0.5])[0.5],
p95: variance.histogram.percentiles([0.95])[0.95]
});
// get data for each span
variance.spans.forEach(function (span) {
tmp.spans.push(fclone({
name: span.name,
labels: span.labels,
kind: span.kind,
min: span.histogram.getMin(),
max: span.histogram.getMax(),
median: span.histogram.percentiles([0.5])[0.5]
}));
});
// push serialized into normalized data
routeCopy.variances.push(tmp);
});
// push the route into normalized data
normalized[app_name].data.routes.push(routeCopy);
});
});
return normalized;
};
/**
* Check if the string can be a id of some sort
*
* @param {String} id
*/
this.isIdentifier = function (id) {
id = typeof (id) !== 'string' ? id + '' : id;
// uuid v1/v4 with/without dash
if (id.match(/[0-9a-f]{8}-[0-9a-f]{4}-[14][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{12}[14][0-9a-f]{19}/i))
return true;
// if number
else if (id.match(/\d+/))
return true;
// if suit of nbr/letters
else if (id.match(/[0-9]+[a-z]+|[a-z]+[0-9]+/))
return true;
// if match pattern with multiple char spaced by . - _ @
else if (id.match(/((?:[0-9a-zA-Z]+[@\-_.][0-9a-zA-Z]+|[0-9a-zA-Z]+[@\-_.]|[@\-_.][0-9a-zA-Z]+)+)/))
return true;
else
return false;
}
var REGEX_JSON_CLEANUP = /":(?!\[|{)\\"[^"]*\\"|":(["'])(?:(?=(\\?))\2.)*?\1|":(?!\[|{)[^,}\]]*|":\[[^{]*]/g
/**
* Cleanup trace data
* - delete result(s)
* - replace labels value with a question mark
*
* @param {Object} spans list of span for a trace
*/
this.censorSpans = function(spans) {
if (!spans)
return log('spans is null');
if (cst.DEBUG) return;
spans.forEach(function(span) {
if (!span.labels)
return;
delete span.labels.results;
delete span.labels.result;
delete span.spanId;
delete span.parentSpanId;
delete span.labels.values;
Object.keys(span.labels).forEach(function(key) {
if (typeof(span.labels[key]) === 'string' && key !== 'stacktrace')
span.labels[key] = span.labels[key].replace(REGEX_JSON_CLEANUP, '\": \"?\"');
});
});
}
/**
* Parse stackrace of spans to extract and normalize data
*
* @param {Object} spans list of span for a trace
*/
this.parseStacktrace = function (spans) {
var self = this;
if (!spans)
return log('spans is null');
spans.forEach(function (span) {
// if empty make sure that it doesnt exist
if (!span ||
!span.labels ||
!span.labels.stacktrace ||
typeof(span.labels.stacktrace) !== 'string')
return;
// you never know what come through that door
try {
span.labels.stacktrace = JSON.parse(span.labels.stacktrace);
} catch (e) {
return ;
}
if (!span.labels.stacktrace || !(span.labels.stacktrace.stack_frame instanceof Array) ) return ;
// parse the stacktrace
var result = self.stackParser.parse(span.labels.stacktrace.stack_frame);
if (result) {
span.labels['source/file'] = result.callsite || undefined;
span.labels['source/context'] = result.context || undefined;
}
});
spans.forEach(function (span) {
if (!span || !span.labels)
return;
delete span.labels.stacktrace;
})
}
};

View File

@ -1,235 +0,0 @@
var path = require('path');
var isAbsolute = require('../tools/IsAbsolute.js');
// EWMA = ExponentiallyWeightedMovingAverage from
// https://github.com/felixge/node-measured/blob/master/lib/util/ExponentiallyMovingWeightedAverage.js
// used to compute the nbr of time per minute that a variance is hit by a new trace
function EWMA () {
this._timePeriod = 60000
this._tickInterval = 5000
this._alpha = 1 - Math.exp(-this._tickInterval / this._timePeriod)
this._count = 0
this._rate = 0
var self = this
this._interval = setInterval(function () {
self.tick()
}, this._tickInterval)
this._interval.unref()
}
EWMA.prototype.update = function (n) {
this._count += n || 1
}
EWMA.prototype.tick = function () {
var instantRate = this._count / this._tickInterval
this._count = 0
this._rate += (this._alpha * (instantRate - this._rate))
}
EWMA.prototype.rate = function (timeUnit) {
return (this._rate || 0) * timeUnit
}
var moment = require('moment');
/**
* Simple cache implementation
*
* @param {Object} opts cache options
* @param {Integer} opts.ttl time to live of all the keys
* @param {Function} opts.miss function called when a key isn't found in the cache
*/
function Cache (opts) {
this._cache = {};
this._miss = opts.miss;
this._ttl_time = opts.ttl;
this._ttl = {};
if (opts.ttl) {
setInterval(this._worker.bind(this), 1000);
}
}
/**
* Task running to check TTL and potentially remove older key
*/
Cache.prototype._worker = function () {
var keys = Object.keys(this._ttl);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = this._ttl[key];
if (moment().isAfter(value)) {
delete this._cache[key];
delete this._ttl[key];
}
}
};
/**
* Empty the cache
*/
Cache.prototype.reset = function () {
this._cache = null;
this._cache = {};
this._ttl = null;
this._ttl = {};
};
/**
* Get a value from the cache
*
* @param {String} key
*/
Cache.prototype.get = function (key) {
if (!key) return null;
var value = this._cache[key];
if (value) return value;
value = this._miss(key);
if (value) {
this.set(key, value);
}
return value;
};
/**
* Set a value in the cache
*
* @param {String} key
* @param {Mixed} value
*/
Cache.prototype.set = function (key, value) {
if (!key || !value) return false;
this._cache[key] = value;
if (this._ttl_time) {
this._ttl[key] = moment().add(this._ttl_time, 'seconds');
}
return true;
};
/**
* StackTraceParser is used to parse callsite from stacktrace
* and get from FS the context of the error (if available)
*
* @param {Cache} cache cache implementation used to query file from FS and get context
*/
function StackTraceParser (opts) {
this._cache = opts.cache;
this._context_size = opts.context;
}
StackTraceParser.prototype.attachContext = function (error) {
var self = this;
if (!error) return error;
// if pmx attached callsites we can parse them to retrieve the context
if (typeof (error.stackframes) === 'object') {
var result = self.parse(error.stackframes);
// no need to send it since there is already the stacktrace
delete error.stackframes;
delete error.__error_callsites;
if (result) {
error.callsite = result.callsite;
error.context = result.context;
}
}
// if the stack is here we can parse it directly from the stack string
// only if the context has been retrieved from elsewhere
if (typeof error.stack === 'string' && !error.callsite) {
var siteRegex = /(\/[^\\\n]*)/g;
var tmp;
var stack = [];
// find matching groups
while ((tmp = siteRegex.exec(error.stack))) {
stack.push(tmp[1]);
}
// parse each callsite to match the format used by the stackParser
stack = stack.map(function (callsite) {
// remove the trailing ) if present
if (callsite[callsite.length - 1] === ')') {
callsite = callsite.substr(0, callsite.length - 1);
}
var location = callsite.split(':');
return location.length < 3 ? callsite : {
file_name: location[0],
line_number: parseInt(location[1])
};
});
var finalCallsite = self.parse(stack);
if (finalCallsite) {
error.callsite = finalCallsite.callsite;
error.context = finalCallsite.context;
}
}
return error;
};
/**
* Parse the stacktrace and return callsite + context if available
*/
StackTraceParser.prototype.parse = function (stack) {
var self = this;
if (!stack || stack.length === 0) return false;
for (var i = 0, len = stack.length; i < len; i++) {
var callsite = stack[i];
// avoid null values
if (typeof callsite !== 'object') continue;
if (!callsite.file_name || !callsite.line_number) continue;
var type = isAbsolute(callsite.file_name) || callsite.file_name[0] === '.' ? 'user' : 'core';
// only use the callsite if its inside user space
if (!callsite || type === 'core' || callsite.file_name.indexOf('node_modules') > -1 ||
callsite.file_name.indexOf('vxx') > -1) {
continue;
}
// get the whole context (all lines) and cache them if necessary
var context = self._cache.get(callsite.file_name);
var source = [];
if (context && context.length > 0) {
// get line before the call
var preLine = callsite.line_number - self._context_size - 1;
var pre = context.slice(preLine > 0 ? preLine : 0, callsite.line_number - 1);
if (pre && pre.length > 0) {
pre.forEach(function (line) {
source.push(line.replace(/\t/g, ' '));
});
}
// get the line where the call has been made
if (context[callsite.line_number - 1]) {
source.push(context[callsite.line_number - 1].replace(/\t/g, ' ').replace(' ', '>>'));
}
// and get the line after the call
var postLine = callsite.line_number + self._context_size;
var post = context.slice(callsite.line_number, postLine);
if (post && post.length > 0) {
post.forEach(function (line) {
source.push(line.replace(/\t/g, ' '));
});
}
}
return {
context: source.length > 0 ? source.join('\n') : 'cannot retrieve source context',
callsite: [ callsite.file_name, callsite.line_number ].join(':')
};
}
return false;
};
module.exports = {
EWMA: EWMA,
Cache: Cache,
StackTraceParser: StackTraceParser
};

View File

@ -1,69 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var PM2 = require('../..');
var debug = require('debug')('interface:watchdog');
var shelljs = require('shelljs');
var csts = require('../../constants');
var path = require('path');
process.env.PM2_AGENT_ONLINE = true;
var WatchDog = module.exports = {
start : function(p) {
var self = this;
this.ipm2 = p.conf.ipm2;
this.relaunching = false;
this.pm2_instance = p.conf.pm2_instance;
/**
* Handle PM2 connection state changes
*/
this.ipm2.on('ready', function() {
console.log('[WATCHDOG] Connected to PM2');
self.relaunching = false;
self.autoDump();
});
console.log('[WATCHDOG] Launching');
this.ipm2.on('reconnecting', function() {
console.log('[WATCHDOG] PM2 is disconnected - Relaunching PM2');
if (self.relaunching === true) return console.log('[WATCHDOG] Already relaunching PM2');
self.relaunching = true;
if (self.dump_interval)
clearInterval(self.dump_interval);
return WatchDog.resurrect();
});
},
resurrect : function() {
var self = this;
console.log('[WATCHDOG] Trying to launch PM2 #1');
shelljs.exec('node ' + path.resolve(__dirname, '../../bin/pm2') + ' resurrect', function() {
setTimeout(function() {
self.relaunching = false;
}, 2500);
});
},
autoDump : function() {
var self = this;
this.dump_interval = setInterval(function() {
if (self.relaunching == true) return false;
self.pm2_instance.dump(function(err) {
if (err) return console.error('[WATCHDOG] Error when dumping');
debug('PM2 process list dumped');
return false;
});
}, 5 * 60 * 1000);
}
};

View File

@ -1,40 +0,0 @@
var os = require('os');
var type = {
v4: {
def: '127.0.0.1',
family: 'IPv4'
},
v6: {
def: '::1',
family: 'IPv6'
}
};
function internalIp(version) {
var options = type[version];
var ret = options.def;
var interfaces = os.networkInterfaces();
Object.keys(interfaces).forEach(function (el) {
interfaces[el].forEach(function (el2) {
if (!el2.internal && el2.family === options.family) {
ret = el2.address;
}
});
});
return ret;
}
function v4() {
return internalIp('v4');
}
function v6() {
return internalIp('v6');
}
module.exports = v4;
module.exports.v4 = v4;
module.exports.v6 = v6;

View File

@ -1,122 +0,0 @@
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
/**
* Dependencies
*/
var axon = require('pm2-axon');
var cst = require('../../constants.js');
var util = require('util');
var rpc = require('pm2-axon-rpc');
var log = require('debug')('pm2:interface');
var EventEmitter = require('events').EventEmitter;
/**
* Export with conf
*/
module.exports = function(opts){
var sub_port = opts && opts.sub_port || cst.DAEMON_PUB_PORT;
var rpc_port = opts && opts.rpc_port || cst.DAEMON_RPC_PORT;
return new IPM2(sub_port, rpc_port);
};
/**
* IPM2, Pm2 Interface
*/
var IPM2 = function(sub_port, rpc_port) {
if (!(this instanceof IPM2)) return new IPM2(sub_port, rpc_port);
var self = this;
EventEmitter.call(this);
this.sub_port = sub_port;
this.rpc_port = rpc_port;
var sub = axon.socket('sub-emitter');
var sub_sock = this.sub_sock = sub.connect(sub_port);
this.bus = sub;
var req = axon.socket('req');
var rpc_sock = this.rpc_sock = req.connect(rpc_port);
this.rpc_client = new rpc.Client(req);
this.rpc = {};
rpc_sock.on('connect', function() {
log('rpc_sock:ready');
self.emit('rpc_sock:ready');
generateMethods(function() {
self.emit('ready');
});
});
rpc_sock.on('close', function() {
log('rpc_sock:closed');
self.emit('close');
});
rpc_sock.on('reconnect attempt', function() {
log('rpc_sock:reconnecting');
self.emit('reconnecting');
});
sub_sock.on('connect', function() {
log('sub_sock ready');
self.emit('sub_sock:ready');
});
sub_sock.on('close', function() {
log('sub_sock:closed');
self.emit('closed');
});
sub_sock.on('reconnect attempt', function() {
log('sub_sock:reconnecting');
self.emit('reconnecting');
});
/**
* Disconnect socket connections. This will allow Node to exit automatically.
* Further calls to PM2 from this object will throw an error.
*/
this.disconnect = function () {
self.sub_sock.close();
self.rpc_sock.close();
};
/**
* Generate method by requesting exposed methods by PM2
* You can now control/interact with PM2
*/
var generateMethods = function(cb) {
log('Requesting and generating RPC methods');
self.rpc_client.methods(function(err, methods) {
Object.keys(methods).forEach(function(key) {
var method_signature, md;
method_signature = md = methods[key];
log('+-- Creating %s method', md.name);
(function(name) {
self.rpc[name] = function() {
log(name);
var args = Array.prototype.slice.call(arguments);
args.unshift(name);
self.rpc_client.call.apply(self.rpc_client, args);
};
})(md.name);
});
return cb();
});
};
};
util.inherits(IPM2, EventEmitter);

View File

@ -30,15 +30,7 @@ delete process.env.pm2_env;
(function ProcessContainer() {
var fs = require('fs');
if (process.env.pmx !== 'false') {
require('pmx').init({
transactions: (process.env.km_link === 'true' && (process.env.trace === 'true' || process.env.deep_monitoring === 'true')) || false,
http: process.env.km_link === 'true' || false,
v8: process.env.v8 === 'true' || process.env.deep_monitoring === 'true' || false,
event_loop_dump: process.env.event_loop_inspector === 'true' || process.env.deep_monitoring === 'true' || false,
deep_metrics: process.env.deep_monitoring === 'true' || false
});
}
require('./ProcessUtils').injectModules();
var stdFile = pm2_env.pm_log_path;
var outFile = pm2_env.pm_out_log_path;

View File

@ -1,18 +1,10 @@
/**
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
// Inject custom modules
if (process.env.pmx !== 'false') {
require('pmx').init({
transactions: (process.env.km_link === 'true' && (process.env.trace === 'true' || process.env.deep_monitoring === 'true')) || false,
http: process.env.km_link === 'true' || false,
v8: process.env.v8 === 'true' || process.env.deep_monitoring === 'true' || false,
event_loop_dump: process.env.event_loop_inspector === 'true' || process.env.deep_monitoring === 'true' || false,
deep_metrics: process.env.deep_monitoring === 'true' || false
});
}
require('./ProcessUtils').injectModules();
if (typeof(process.env.source_map_support) != "undefined" &&
process.env.source_map_support !== "false") {

43
lib/ProcessUtils.js Normal file
View File

@ -0,0 +1,43 @@
'use strict'
module.exports = {
injectModules: function() {
if (process.env.pmx !== 'false') {
const pmx = require('@pm2/io');
let conf = {};
if (process.env.io) {
const io = JSON.parse(process.env.io);
conf = io.conf ? io.conf : conf;
}
let defaultConf = {
transactions: (process.env.trace === 'true' || process.env.deep_monitoring === 'true') || false,
http: process.env.km_link === 'true' || false,
v8: process.env.v8 === 'true' || process.env.deep_monitoring === 'true' || false,
event_loop_dump: process.env.event_loop_inspector === 'true' || process.env.deep_monitoring === 'true' || false,
deep_metrics: process.env.deep_monitoring === 'true' || false
};
const mergedConf = Object.assign(defaultConf, conf);
pmx.init(mergedConf);
if(require('semver').satisfies(process.versions.node, '>= 8.0.0')) {
var url = '';
pmx.action('internal:inspect', function(reply) {
const inspector = require('inspector');
if(url === '') {
inspector.open();
url = inspector.url();
} else {
inspector.close();
url = '';
}
reply(url);
});
}
}
}
};

View File

@ -215,7 +215,6 @@ Satan.remoteWrapper = function() {
killMe : God.killMe,
notifyKillPM2 : God.notifyKillPM2,
forceGc : God.forceGc,
findByFullPath : God.findByFullPath,
@ -329,7 +328,7 @@ Satan.launchDaemon = function launchDaemon(cb) {
debug('Launching daemon');
var SatanJS = p.resolve(p.dirname(module.filename), 'Satan.js');
var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js');
var InteractorDaemonizer = require('@pm2/agent/src/InteractorClient');
var node_args = [];
@ -385,7 +384,7 @@ Satan.launchDaemon = function launchDaemon(cb) {
debug('PM2 daemon launched with return message: ', msg);
child.removeListener('error', onError);
child.disconnect();
InteractorDaemonizer.launchAndInteract({}, function(err, data) {
InteractorDaemonizer.launchAndInteract({}, {}, function(err, data) {
if (data)
debug('Interactor launched');
return cb ? cb(null, child) : false;

View File

@ -247,6 +247,18 @@ var Utility = module.exports = {
checkPathIsNull: function(path) {
return path === 'NULL' || path === '/dev/null';
},
generateUUID: function () {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4";
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = "-";
return s.join("");
}
};

View File

@ -20,9 +20,9 @@ commander.version(pkg.version)
.option('--auto-manage', 'keep application online after command exit')
.option('--fast-boot', 'boot app faster by keeping pm2 runtime online in background (effective at second exit/start)')
.option('--web [port]', 'launch process web api on [port] default to 9615')
.option('--secret [key]', 'keymetrics secret key')
.option('--public [key]', 'keymetrics public key')
.option('--machine-name [name]', 'keymetrics machine name')
.option('--secret [key]', 'PM2 plus secret key')
.option('--public [key]', 'PM2 plus public key')
.option('--machine-name [name]', 'PM2 plus machine name')
.option('--env [name]', 'select env_[name] env variables in process config file')
.option('--watch', 'Watch and Restart')
.option('-i --instances <number>', 'launch [number] instances with load-balancer')
@ -32,9 +32,9 @@ commander.command('*')
.action(function(cmd){
pm2 = new PM2.custom({
pm2_home : path.join(process.env.HOME, '.pm3'),
secret_key : process.env.KEYMETRICS_SECRET || commander.secret,
public_key : process.env.KEYMETRICS_PUBLIC || commander.public,
machine_name : process.env.INSTANCE_NAME || commander.machineName
secret_key : cst.SECRET_KEY || commander.secret,
public_key : cst.PUBLIC_KEY || commander.public,
machine_name : cst.MACHINE_NAME || commander.machineName
});
pm2.connect(function() {

View File

@ -17,15 +17,15 @@ process.env.PM2_DISCRETE_MODE = true;
commander.version(pkg.version)
.description('pm2-runtime is a drop-in replacement Node.js binary for containers')
.option('-i --instances <number>', 'launch [number] of processes automatically load-balanced. Increase overall performances and performance stability.')
.option('--secret [key]', '[MONITORING] keymetrics secret key')
.option('--secret [key]', '[MONITORING] PM2 plus secret key')
.option('--no-autorestart', 'start an app without automatic restart')
.option('--node-args <node_args>', 'space delimited arguments to pass to node in cluster mode - e.g. --node-args="--debug=7001 --trace-deprecation"')
.option('-n --name <name>', 'set a <name> for script')
.option('--max-memory-restart <memory>', 'specify max memory amount used to autorestart (in octet or use syntax like 100M)')
.option('-c --cron <cron_pattern>', 'restart a running process based on a cron pattern')
.option('--interpreter <interpreter>', 'the interpreter pm2 should use for executing app (bash, python...)')
.option('--public [key]', '[MONITORING] keymetrics public key')
.option('--machine-name [name]', '[MONITORING] keymetrics machine name')
.option('--public [key]', '[MONITORING] PM2 plus public key')
.option('--machine-name [name]', '[MONITORING] PM2 plus machine name')
.option('--trace', 'enable transaction tracing with km')
.option('--v8', 'enable v8 data collecting')
.option('--format', 'output logs formated like key=val')
@ -65,9 +65,9 @@ var Runtime = {
instanciate : function(cmd) {
this.pm2 = new PM2.custom({
pm2_home : process.env.PM2_HOME || path.join(process.env.HOME, '.pm2'),
secret_key : process.env.KEYMETRICS_SECRET || commander.secret,
public_key : process.env.KEYMETRICS_PUBLIC || commander.public,
machine_name : process.env.INSTANCE_NAME || commander.machineName,
secret_key : cst.SECRET_KEY || commander.secret,
public_key : cst.PUBLIC_KEY || commander.public,
machine_name : cst.MACHINE_NAME || commander.machineName,
daemon_mode : process.env.PM2_RUNTIME_DEBUG || false
});

View File

@ -12,12 +12,11 @@ __/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____
_\///______________\///______________\///__\///////////////__
Community Edition
Runtime Edition
Production Process Manager for Node.js applications
PM2 is a Production Process Manager for Node.js applications
with a built-in Load Balancer.
Start and Daemonize any application:
$ pm2 start app.js

View File

@ -1,33 +1,15 @@
module.exports = {
/**
* Application configuration section
* http://pm2.keymetrics.io/docs/usage/application-declaration/
*/
apps : [
// First application
{
name : 'API',
script : 'app.js',
env: {
COMMON_VARIABLE: 'true'
},
env_production : {
NODE_ENV: 'production'
}
apps : [{
name : 'API',
script : 'app.js',
env: {
NODE_ENV: 'development'
},
// Second application
{
name : 'WEB',
script : 'web.js'
env_production : {
NODE_ENV: 'production'
}
],
}],
/**
* Deployment section
* http://pm2.keymetrics.io/docs/usage/deployment/
*/
deploy : {
production : {
user : 'node',
@ -36,17 +18,6 @@ module.exports = {
repo : 'git@github.com:repo.git',
path : '/var/www/production',
'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production'
},
dev : {
user : 'node',
host : '212.83.163.1',
ref : 'origin/master',
repo : 'git@github.com:repo.git',
path : '/var/www/development',
'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env dev',
env : {
NODE_ENV: 'dev'
}
}
}
};

View File

@ -0,0 +1,41 @@
#!/bin/sh
#
# from /usr/ports/infrastructure/templates/rc.template
daemon="/usr/local/bin/pm2"
#daemon_flags=
#daemon_rtable=0
#daemon_timeout="30"
daemon_user="%USER%"
. /etc/rc.d/rc.subr
pexp="node: PM2.*God Daemon.*"
#rc_bg= # (undefined)
#rc_reload= # (undefined)
#rc_usercheck=YES
#rc_pre() {
#}
rc_start() {
${rcexec} "${daemon} ${daemon_flags} resurrect"
}
#rc_check() {
# pgrep -T "${daemon_rtable}" -q -xf "${pexp}"
#}
rc_reload() {
${rcexec} "${daemon} reload all"
#pkill -HUP -T "${daemon_rtable}" -xf "${pexp}"
}
#rc_stop() {
# pkill -T "${daemon_rtable}" -xf "${pexp}"
#}
#rc_post() {
#}
rc_cmd $1

View File

@ -18,7 +18,7 @@ module.exports = function(bytes, size) {
}
var descriptor = fs.openSync(file, 'r');
try {
bytes = new Buffer(max_bytes);
bytes = Buffer.alloc(max_bytes);
size = fs.readSync(descriptor, bytes, 0, bytes.length, 0);
} finally {
fs.closeSync(descriptor);
@ -32,7 +32,7 @@ module.exports = function(bytes, size) {
fs.open(file, 'r', function(err, descriptor){
if (err) return callback(err);
var bytes = new Buffer(max_bytes);
var bytes = Buffer.alloc(max_bytes);
// Read the file with no encoding for raw buffer access.
fs.read(descriptor, bytes, 0, bytes.length, 0, function(err, size, bytes){
fs.close(descriptor, function(err2){

View File

@ -1,9 +1,9 @@
{
"name": "pm2",
"preferGlobal": true,
"version": "2.10.4",
"version": "3.0.0",
"engines": {
"node": ">=0.12"
"node": ">=4.0.0"
},
"directories": {
"bin": "./bin",
@ -12,25 +12,25 @@
},
"author": {
"name": "Strzelewicz Alexandre",
"email": "alexandre@keymetrics.io",
"url": "https://keymetrics.io"
"email": "alexandre@pm2.io",
"url": "https://pm2.io"
},
"maintainers": [
{
"name": "tknew",
"email": "strzelewicz.alexandre@gmail.com"
"name": "Alexandre Strzelewicz",
"email": "alexandre@pm2.io"
},
{
"name": "soyuka",
"email": "soyuka@gmail.com"
"name": "Antoine Bluchet",
"email": "antoine@pm2.io"
},
{
"name": "wallet77",
"email": "wallet77@gmail.com"
"name": "Vincent Vallet",
"email": "vincent@pm2.io"
},
{
"name": "vmarchaud",
"email": "contact@vmarchaud.fr"
"name": "Valentin Marchaud",
"email": "valentin@pm2.io"
}
],
"contributors": [
@ -91,8 +91,7 @@
"main": "index.js",
"types": "types/index.d.ts",
"scripts": {
"test": "NODE_ENV=test bash test/pm2_check_dependencies.sh && NODE_ENV=test bash test/pm2_programmatic_tests.sh && NODE_ENV=test bash test/pm2_behavior_tests.sh",
"bench-pmx": "pm2 delete all; pm2 install pm2-probe; node examples/pmx/app.js; pm2 ls"
"test": "bash test/e2e.sh && bash test/unit.sh"
},
"keywords": [
"cli",
@ -159,38 +158,41 @@
"pm2-runtime": "./bin/pm2-runtime"
},
"dependencies": {
"async": "^2.5",
"@pm2/agent": "^0.5.4",
"@pm2/io": "^2.0.2",
"@pm2/js-api": "^0.5.15",
"async": "^2.6.1",
"blessed": "^0.1.81",
"chalk": "^1.1",
"chokidar": "^2",
"chalk": "^2.4.1",
"chokidar": "^2.0.4",
"cli-table-redemption": "^1.0.0",
"commander": "2.13.0",
"coffee-script": "^1.12.7",
"commander": "2.15.1",
"cron": "^1.3",
"debug": "^3.0",
"eventemitter2": "1.0.5",
"debug": "^3.1",
"eventemitter2": "5.0.1",
"fclone": "1.0.11",
"mkdirp": "0.5.1",
"moment": "^2.19",
"needle": "^2.1.0",
"moment": "^2.22.2",
"needle": "^2.2.1",
"nssocket": "0.6.0",
"pidusage": "^1.2.0",
"pm2-axon": "3.1.0",
"pidusage": "^2.0.6",
"pm2-axon": "3.3.0",
"pm2-axon-rpc": "^0.5.1",
"pm2-deploy": "^0.3.9",
"pm2-multimeter": "^0.1.2",
"pmx": "^1.6",
"promptly": "2.2.0",
"semver": "^5.3",
"shelljs": "0.7.8",
"source-map-support": "^0.5",
"promptly": "3.0.3",
"semver": "^5.5",
"shelljs": "~0.8.2",
"source-map-support": "^0.5.6",
"sprintf-js": "1.1.1",
"v8-compile-cache": "^1.1.0",
"vizion": "^0.2",
"v8-compile-cache": "^2.0.0",
"vizion": "~0.2.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
"mocha": "^3.5",
"should": "^11"
"mocha": "^5.2.0",
"should": "^13"
},
"optionalDependencies": {
"gkt": "https://tgz.pm2.io/gkt-1.0.0.tgz"

View File

@ -45,7 +45,7 @@ module.exports = function(PM2_HOME) {
DEFAULT_PID_PATH : p.resolve(PM2_HOME, 'pids'),
DEFAULT_LOG_PATH : p.resolve(PM2_HOME, 'logs'),
DEFAULT_MODULE_PATH : p.resolve(PM2_HOME, 'modules'),
KM_ACCESS_TOKEN : p.resolve(PM2_HOME, 'km-access-token'),
PM2_IO_ACCESS_TOKEN : p.resolve(PM2_HOME, 'pm2-io-token'),
DUMP_FILE_PATH : p.resolve(PM2_HOME, 'dump.pm2'),
DUMP_BACKUP_FILE_PATH : p.resolve(PM2_HOME, 'dump.pm2.bak'),

View File

@ -1,9 +1,14 @@
FROM node:9
FROM node:alpine
RUN mkdir -p /var/pm2
WORKDIR /var/pm2
ENV NODE_ENV test
ENV PM2_DISCRETE_MODE true
RUN apk update && apk add bash git curl python python3 php5 && rm -rf /var/cache/apk/*
RUN ln -s /usr/bin/php5 /usr/bin/php
RUN npm install -g mocha@3.5
CMD ["mocha", "./test/programmatic/api.mocha.js"]

View File

@ -1,24 +0,0 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
echo -e "\033[1mRunning tests:\033[0m"
which wrk
spec "You should have wrk benchmark in your /usr/bin"
killall node
cd $file_path
$pm2 start cluster-pm2.json
$pm2 start cluster-pm2.json -f
$pm2 start cluster-pm2.json -f
$pm2 start cluster-pm2.json -f
spec "start cluster"
wrk -c 500 -t 500 -d 8 http://localhost:8020 &> /dev/null &
$pm2 monit
$pm2 list
$pm2 stop

View File

@ -1,53 +0,0 @@
#!/usr/bin/env bash
#
# LSOF check
#
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
cd $file_path
echo "################## RELOAD ###################"
lsof -c PM2 > /tmp/no_pm2_out.dat
$pm2 list
sleep 3
lsof -c PM2 > /tmp/empty_pm2_out.dat
$pm2 start echo.js -i 3
$pm2 start killtoofast.js -i 3
$pm2 delete all
sleep 3
lsof -c PM2 > /tmp/empty_pm2_out2.dat
OUT1=`cat /tmp/empty_pm2_out.dat | wc -l`
OUT2=`cat /tmp/empty_pm2_out2.dat | wc -l`
if [ $OUT1 -eq $OUT2 ]; then
success "All file descriptors have been closed"
else
fail "Some file descriptors are still open"
fi
$pm2 start killtoofast.js -i 6
$pm2 kill
sleep 3
lsof -c PM2 > /tmp/no_pm2_out2.dat
diff /tmp/no_pm2_out.dat /tmp/no_pm2_out2.dat
if [ $? == "0" ]; then
success "All file descriptors have been closed"
else
fail "Some file descriptors are still open"
fi
rm /tmp/no_pm2_out.dat
rm /tmp/no_pm2_out2.dat
rm /tmp/empty_pm2_out.dat
rm /tmp/empty_pm2_out2.dat

View File

@ -1,35 +0,0 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
cd $file_path
echo "################## GRACEFUL RELOAD ###################"
###############
echo "Launching"
$pm2 start graceful-exit.js -i 4 --name="graceful" -o "grace.log" -e "grace-err.log"
should 'should start processes' 'online' 4
OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"`
cat /dev/null > $OUT_LOG
#### Graceful reload all
$pm2 gracefulReload all
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
[ $OUT -eq 1 ] || fail "Process not restarted gracefuly"
success "Process restarted gracefuly"
cat /dev/null > $OUT_LOG
#### Graceful reload name
$pm2 gracefulReload graceful
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
[ $OUT -eq 1 ] || fail "Process not restarted gracefuly"
success "Process restarted gracefuly"

View File

@ -1,28 +0,0 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
cd $file_path
echo "################## GRACEFUL RELOAD 2 ###################"
echo "Launching"
$pm2 start graceful-exit-no-listen.js -i 2 --name="graceful2" -o "grace2.log" -e "grace-err2.log"
should 'should start processes' 'online' 2
OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"`
cat /dev/null > $OUT_LOG
#### Graceful reload name
$pm2 gracefulReload graceful2
echo "PATH: $OUT_LOG"
TEXT=$(cat $OUT_LOG)
echo "TEXT: $TEXT"
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
[ $OUT -eq 1 ] || fail "Non-listening process not restarted gracefuly"
success "Non-listening process restarted gracefuly"

View File

@ -1,22 +0,0 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
cd $file_path
echo "################## GRACEFUL RELOAD 3 ###################"
echo "Launching"
$pm2 start graceful-exit-send.js -i 2 --name="graceful3" -o "grace3.log" -e "grace-err3.log"
should 'should start processes' 'online' 2
OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"`
cat /dev/null > $OUT_LOG
#### Graceful reload name
$pm2 gracefulReload graceful3
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
[ $OUT -eq 1 ] || fail "Process that sends 'online' not restarted gracefuly"
success "Process that sends 'online' restarted gracefuly"

View File

@ -1,32 +0,0 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
cd $file_path
echo -e "\033[1mRunning Interaction tests:\033[0m"
$pm2 interact stop
$pm2 interact
$pm2 interact XXX2 XXX3 homeloc
$pm2 updatePM2
$pm2 interact stop
$pm2 interact
$pm2 interact info
$pm2 interact info | grep "XXX2"
spec "Should have XXX2 has public key"
$pm2 interact info | grep "XXX3"
spec "Should have XXX3 has public key"
$pm2 list
$pm2 interact stop
$pm2 kill

View File

@ -1,18 +0,0 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/../include.sh"
cd $file_path
echo -e "\033[1mRunning tests for json files :\033[0m"
$pm2 start echo.js --name zero -f
$pm2 start echo.js --name one -f
$pm2 start echo.js --name two -f
should 'should have 3 processes online' 'online' 3
$pm2 stop 0
$pm2 stop 2
$pm2 start echo.js --name three -f
$pm2 ls
should 'should have 2 processes online' 'online' 2
should 'should have 2 processes stopped' 'stopped' 2

View File

@ -1,3 +1,10 @@
# docker build -t pm2-test -f test/Dockerfile .
docker run -v `pwd`:/var/pm2 pm2-test mocha ./test/programmatic/api.mocha.js
docker run -v `pwd`:/var/pm2 pm2-test mocha ./test/programmatic/client.mocha.js
set -e
docker build -t pm2-test -f test/Dockerfile .
JOBS=2
OPTS="--jobs $JOBS --joblog joblog-X docker run -v `pwd`:/var/pm2 pm2-test"
ls test/e2e/cli/* | parallel $OPTS bash
#ls test/e2e/binaries/* test/e2e/modules/* test/e2e/internal/* test/e2e/process-file/* test/e2e/cli/* test/e2e/logs/* | parallel $OPTS bash

144
test/e2e.sh Normal file
View File

@ -0,0 +1,144 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/e2e/include.sh"
# Abort script at first error
set -e
set -o verbose
# MODULES
bash ./test/e2e/modules/get-set.sh
spec "Configuration system working"
bash ./test/e2e/modules/module.sh
spec "module system"
bash ./test/e2e/modules/module-safeguard.sh
spec "module safeguard system (--safe)"
# CLI
bash ./test/e2e/cli/reload.sh
spec "Reload"
bash ./test/e2e/cli/start-app.sh
spec "Command line passing"
bash ./test/e2e/cli/operate-regex.sh
spec "Operate process that match regex"
bash ./test/e2e/cli/interpreter.sh
spec "Javascript transpilers tests"
bash ./test/e2e/cli/app-configuration.sh
spec "App configuration"
bash ./test/e2e/cli/binary.sh
spec "binary test"
bash ./test/e2e/cli/startOrX.sh
spec "startOrX commands"
bash ./test/e2e/cli/reset.sh
spec "Reset meta"
bash ./test/e2e/cli/env-refresh.sh
spec "Environment refresh on restart"
bash ./test/e2e/cli/extra-lang.sh
spec "Various programming languages checks (Python, PHP)"
bash ./test/e2e/cli/multiparam.sh
spec "Multiparam process management"
bash ./test/e2e/cli/smart-start.sh
spec "smart start test"
bash ./test/e2e/cli/args.sh
spec "check arguments passing"
bash ./test/e2e/cli/attach.sh
spec "pm2 attach method"
bash ./test/e2e/cli/serve.sh
spec "pm2 serve CLI method"
bash ./test/e2e/cli/monit.sh
spec "km selective monitoring "
bash ./test/e2e/cli/cli-actions-1.sh
spec "CLI basic test"
bash ./test/e2e/cli/cli-actions-2.sh
spec "Second hard cli tests"
bash ./test/e2e/cli/dump.sh
spec "dump test"
bash ./test/e2e/cli/resurrect.sh
spec "resurrect test"
bash ./test/e2e/cli/mjs.sh
spec "Test import syntax"
bash ./test/e2e/cli/watch.sh
spec "watch system tests"
bash ./test/e2e/cli/right-exit-code.sh
spec "Verification exit code"
bash ./test/e2e/cli/harmony.sh
spec "Harmony test"
bash ./test/e2e/cli/fork.sh
spec "Fork system working"
bash ./test/e2e/cli/piped-config.sh
spec "Piped JSON file test"
# PROCESS FILES
bash ./test/e2e/process-file/json-file.sh
spec "JSON file test"
bash ./test/e2e/process-file/yaml-configuration.sh
spec "YAML configuration support"
bash ./test/e2e/process-file/json-reload.sh
spec "JSON reload test"
bash ./test/e2e/process-file/homogen-json-action.sh
spec "Homogen json actions"
bash ./test/e2e/process-file/app-config-update.sh
spec "CLI/JSON argument reload"
bash ./test/e2e/process-file/js-configuration.sh
spec "js configuration support"
# BINARIES
bash ./test/e2e/binaries/pm2-dev.sh
spec "pm2-dev"
bash ./test/e2e/binaries/pm2-runtime.sh
spec "pm2-runtime"
# INTERNALS
bash ./test/e2e/internals/wait-ready-event.sh
spec "Wait for application ready event"
bash ./test/e2e/internals/daemon-paths-override.sh
spec "Override daemon configuration paths"
bash ./test/e2e/internals/source_map.sh
spec "Source map resolution on exception"
bash ./test/e2e/internals/wrapped-fork.sh
spec "wrapped fork"
bash ./test/e2e/internals/infinite-loop.sh
spec "Infinite loop stop"
bash ./test/e2e/internals/options-via-env.sh
spec "set option via environment"
bash ./test/e2e/internals/promise.sh
spec "Promise warning message tests"
bash ./test/e2e/internals/increment-var.sh
spec "Increment env variables"
bash ./test/e2e/internals/start-consistency.sh
spec "Consistency between a JSON an CLI start"
# MISC
bash ./test/e2e/misc/inside-pm2.sh
spec "Starting a process inside a PM2 process"
bash ./test/e2e/misc/vizion.sh
spec "vizion features (versioning control)"
bash ./test/e2e/misc/misc.sh
spec "MISC features"
bash ./test/e2e/misc/versioning-cmd.sh
spec "versioning system tests"
bash ./test/e2e/misc/instance-number.sh
spec "Negative instance number spawn one worker"
bash ./test/e2e/misc/startup.sh
spec "upstart startup test"
bash ./test/e2e/misc/nvm-node-version.sh
spec "NVM node version setting"
bash ./test/e2e/misc/cron-system.sh
spec "Cron system tests"
# LOGS
bash ./test/e2e/logs/log-timestamp.sh
spec "timestamp prefix of pm2.log"
bash ./test/e2e/logs/log-custom.sh
spec "Custom log timestamp"
bash ./test/e2e/logs/log-reload.sh
spec "Log reload"
bash ./test/e2e/logs/log-entire.sh
spec "merge stdout && stderr"
bash ./test/e2e/logs/log-null.sh
spec "Logging path set to null"
bash ./test/e2e/logs/log-json.sh
spec "Logging directly to file in json"
$pm2 kill

View File

@ -1,9 +1,21 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
pm2dev="`type -P node` `pwd`/bin/pm2-dev"
pm2_path=`pwd`/bin/pm2-dev
if [ ! -f $pm2_path ];
then
pm2_path=`pwd`/../bin/pm2-dev
if [ ! -f $pm2_path ];
then
pm2_path=`pwd`/../../bin/pm2-dev
fi
fi
pm2dev="`type -P node` $pm2_path"
export PM2_HOME=$HOME'/.pm2-dev'

View File

@ -1,9 +1,21 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
pm2_runtime="`type -P node` `pwd`/bin/pm2-runtime"
pm2_path=`pwd`/bin/pm2-runtime
if [ ! -f $pm2_path ];
then
pm2_path=`pwd`/../bin/pm2-runtime
if [ ! -f $pm2_path ];
then
pm2_path=`pwd`/../../bin/pm2-runtime
fi
fi
pm2_runtime="`type -P node` $pm2_path"
export PM2_RUNTIME_DEBUG='true'

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
echo -e "\033[1mRunning tests:\033[0m"
@ -59,7 +59,6 @@ exists 'probe test-probe exist' "test-probe"
exists 'probe Event Loop Latency exist' "Loop delay"
exists 'probe Event Loop Latency default value' "agg_type: 'avg'"
exists 'probe Event Loop Latency default value' "alert: {}"
# Set new value for alert probe
# $pm2 set probe-test.probes.Event\ Loop\ Latency.value 25
@ -68,12 +67,5 @@ exists 'probe Event Loop Latency default value' "alert: {}"
# exists 'probe Event Loop Latency alerted' "alert: { cmp: '>', value: 25, mode: 'threshold'"
# Override value for test-probe
$pm2 set probe-test.probes.test-probe.value 30
sleep 2
exists 'probe Event Loop Latency alerted' "value: 30"
$pm2 restart all
sleep 1
exists 'probe Event Loop Latency alerted' "value: 30"
# $pm2 set probe-test.probes.test-probe.value 30
# sleep 1

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
cd $file_path/args

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
echo -e "\033[1mRunning tests:\033[0m"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
echo -e "\033[1mRunning tests:\033[0m"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
cd $file_path

View File

@ -1,11 +1,11 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
cd $file_path
############# TEST
############# Start / Stop / Restart
echo -e "\033[1mRunning tests:\033[0m"
@ -17,7 +17,8 @@ spec "Should stop an app by script.js"
$pm2 restart echo.js
spec "Should restart an app by script.js (TRANSITIONAL STATE)"
###############
############### Start edge case
$pm2 delete all
echo "Start application with filename starting with a numeric"
@ -28,28 +29,29 @@ should 'should app be stopped' 'stopped' 1
$pm2 restart 001-test
should 'should app be online once restart called' 'online' 1
$pm2 delete all
############## PID
$pm2 delete all
$pm2 start 001-test.js --name "test"
should 'should app be online' 'online' 1
$pm2 pid > /tmp/pid-tmp
$pm2 pid test
$pm2 delete all
###############
$pm2 delete all
echo "Start application with filename starting with a numeric"
$pm2 start throw-string.js -l err-string.log --merge-logs --no-automation
>err-string.log
sleep 2
sleep 1
grep 'throw-string.js' err-string.log
spec "Should have written raw stack when throwing a string"
$pm2 delete all
####
$pm2 delete all
$pm2 start echo.js --name gege
should 'should app be online' 'online' 1
$pm2 stop gege
@ -78,7 +80,7 @@ $pm2 start echo.js
ispec "Should not re start app"
########### DELETED STUFF BY ID
$pm2 kill
$pm2 delete all
$pm2 start echo.js
$pm2 delete 0
@ -100,7 +102,7 @@ $pm2 list
should 'should has been deleted process by script' "name: 'echo'" 0
######## Actions on app name as number (#1937)
$pm2 kill
$pm2 delete all
$pm2 start echo.js --name "455"
should 'should restart processes' 'restart_time: 0' 1
$pm2 restart 455
@ -113,9 +115,9 @@ $pm2 delete 455
should 'should has been deleted process by id' "name: '455'" 0
########### OPTIONS OUTPUT FILES
$pm2 kill
$pm2 delete all
$pm2 start echo.js -o outech.log -e errech.log --name gmail -i 10
$pm2 start echo.js -o outech.log -e errech.log --name gmail -i 1
sleep 1
cat outech-0.log > /dev/null
spec "file outech-0.log exist"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
cd $file_path
$pm2 start echo.js -i 4

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
source "${SRC}/../include.sh"
cd $file_path
echo -e "\033[1mENV REFRESH\033[0m"

Some files were not shown because too many files have changed in this diff Show More