mirror of
https://github.com/prometheus/statsd_exporter.git
synced 2024-09-25 12:20:01 +00:00
Compare commits
888 commits
Author | SHA1 | Date | |
---|---|---|---|
|
eccf511051 | ||
|
658dadba2f | ||
|
77d911b7fe | ||
|
c7a3f16195 | ||
|
c818915ae3 | ||
|
32f8087bcc | ||
|
2d0de3fba8 | ||
|
cfb74254cb | ||
|
b967f755be | ||
|
1221aed101 | ||
|
4cef1c0d49 | ||
|
58c8a24608 | ||
|
da51813af4 | ||
|
4fcebbde52 | ||
|
90547ea496 | ||
|
3d4d6a9d5c | ||
|
c5aee286ee | ||
|
2b2623218f | ||
|
060a3a8870 | ||
|
8aec6fdf63 | ||
|
96609f9679 | ||
|
468af47dfb | ||
|
da94065b43 | ||
|
ba68944bec | ||
|
7b4f7310ae | ||
|
921ad0771f | ||
|
dcb9cc9446 | ||
|
905f00863b | ||
|
58769c7b4d | ||
|
9c4dfce4e0 | ||
|
29c77f407c | ||
|
233d74d0f7 | ||
|
3f985fa9ac | ||
|
479379a908 | ||
|
6e02dfcaae | ||
|
2c4ffa9620 | ||
|
ac0ef06e65 | ||
|
337849188c | ||
|
4b21c8e662 | ||
|
91ccdb962a | ||
|
d93cb36b75 | ||
|
6483ce0ffe | ||
|
d5473a0f96 | ||
|
b9ee6639ce | ||
|
80810d614e | ||
|
cae614397a | ||
|
e729f64ef3 | ||
|
adeacdd760 | ||
|
a19a729111 | ||
|
4c268bcdf7 | ||
|
8ec3225483 | ||
|
7d28d2145f | ||
|
48b0038897 | ||
|
abc3a1f95c | ||
|
4a0e88e27b | ||
|
7188ed4292 | ||
|
c48fa1bfa7 | ||
|
5d3e63295a | ||
|
8fdc626bfc | ||
|
8adea73c00 | ||
|
a853c2b0b2 | ||
|
2c7fd1edd4 | ||
|
1e89c26ad6 | ||
|
413f482751 | ||
|
f9d238dbdd | ||
|
ef104085db | ||
|
0750ae0346 | ||
|
51cc8e4659 | ||
|
a9b63f9e5f | ||
|
736b8b676b | ||
|
5b8dfc866d | ||
|
3c4f1af3ff | ||
|
4392beadc3 | ||
|
22381d60c5 | ||
|
56fe4f51cd | ||
|
3855b3e8c9 | ||
|
3ea12e444f | ||
|
fb1a02cf4a | ||
|
8373cb382e | ||
|
3d8cac0b30 | ||
|
2337d38124 | ||
|
12d14bb1d8 | ||
|
04ed8c69a9 | ||
|
c246633aec | ||
|
8e326ef3f3 | ||
|
b98a84c3f6 | ||
|
5a947bd1a3 | ||
|
ee8b05b646 | ||
|
3dcff5fed5 | ||
|
aaaf26c074 | ||
|
b0c6d983e1 | ||
|
ccb3eb6277 | ||
|
03255bf218 | ||
|
871e2d8df1 | ||
|
5123a1caa6 | ||
|
3f8b98f180 | ||
|
886c18f947 | ||
|
c76a152291 | ||
|
69bc83fc47 | ||
|
b1094b9061 | ||
|
8ffcdb3d5e | ||
|
029cd6bca5 | ||
|
e91ce201d5 | ||
|
ddf7e0d31d | ||
|
80e119a781 | ||
|
191b07124c | ||
|
f73cc98fd7 | ||
|
7f5125abdd | ||
|
61f201130e | ||
|
2813a69578 | ||
|
2a8dab9248 | ||
|
5daeef52d8 | ||
|
c87a7ac5eb | ||
|
d747c05579 | ||
|
08c4bccb64 | ||
|
a0f4bcff5c | ||
|
fdc8b5f852 | ||
|
c3752cf30f | ||
|
a5b0ed182c | ||
|
972cb24750 | ||
|
24e2fc9980 | ||
|
aa341da7c4 | ||
|
fb5d639dbf | ||
|
14d18d8e70 | ||
|
bbba726e66 | ||
|
f0e9a8e2f8 | ||
|
0bdeac2162 | ||
|
9a980af22f | ||
|
052676c42d | ||
|
21a1763baf | ||
|
832c5a5316 | ||
|
e22a86f5f9 | ||
|
d73608eb14 | ||
|
9303da56a3 | ||
|
2caee689db | ||
|
07cf2fa0b1 | ||
|
f6b2a21fbd | ||
|
c2447be23b | ||
|
b8ace4ae2c | ||
|
fea420de84 | ||
|
9c6e245521 | ||
|
f6ab38f75e | ||
|
559fc9c1af | ||
|
507b0a20dd | ||
|
f77011fd34 | ||
|
fbfa209c87 | ||
|
fc1d834d59 | ||
|
81884eb3d8 | ||
|
f231009802 | ||
|
dbdf4d9349 | ||
|
c74db12eaf | ||
|
fd4ea19c21 | ||
|
1bcb9d1a97 | ||
|
a92aa4556e | ||
|
00d82daaf2 | ||
|
65e7877ab3 | ||
|
aecad1a2fa | ||
|
5d28e6d898 | ||
|
edaa33860e | ||
|
89e7668d8d | ||
|
da06fabcb6 | ||
|
83cec219c8 | ||
|
31b05ef232 | ||
|
c41fa101f5 | ||
|
7afa060ce4 | ||
|
30c3e31574 | ||
|
e85098da3f | ||
|
d0585ec62b | ||
|
af84364004 | ||
|
1e801bc499 | ||
|
b43a60e9c8 | ||
|
bc43c5606d | ||
|
c2505cf91e | ||
|
8f351a5577 | ||
|
b1068d058a | ||
|
3b9ef1fef5 | ||
|
9d4eeda5b1 | ||
|
c2c1af12ab | ||
|
9115f0fa39 | ||
|
6ef0b3b4e8 | ||
|
1a148215de | ||
|
f935a9c869 | ||
|
3a63a4b86c | ||
|
2ab624a917 | ||
|
848212e028 | ||
|
d90c8ff92d | ||
|
6e341dd805 | ||
|
c753dfaf76 | ||
|
0d22f85f04 | ||
|
b8504edbe4 | ||
|
c714dcdf2e | ||
|
dcddbc4234 | ||
|
d46243aa66 | ||
|
4d07e28b7c | ||
|
ada7248ccb | ||
|
0de518d3d5 | ||
|
3427e36d50 | ||
|
99a94fa8b4 | ||
|
4b6983811e | ||
|
e416ec14d3 | ||
|
f8fc001bba | ||
|
542ee7cfa9 | ||
|
0cb34c771d | ||
|
549a96a54c | ||
|
fbf7837387 | ||
|
d015cda365 | ||
|
2d339b0853 | ||
|
60742e1bd6 | ||
|
78dcd3b7b2 | ||
|
4ad11fcafa | ||
|
2f5add464e | ||
|
4a086bc25b | ||
|
55e536bc15 | ||
|
161f982ae2 | ||
|
5654505569 | ||
|
1a1dd2d8a3 | ||
|
b8462d09ec | ||
|
c481b95155 | ||
|
b1222831b6 | ||
|
db4a4f2fbe | ||
|
83180ae37f | ||
|
5c6c8a28d0 | ||
|
a32a9da527 | ||
|
7e2fe6c2e6 | ||
|
7ba3f6a841 | ||
|
6a5a089597 | ||
|
6564725760 | ||
|
bfdf4ddee0 | ||
|
aadb43ed02 | ||
|
258c8c0b2c | ||
|
fae515f739 | ||
|
339c8efb33 | ||
|
16d4f317a5 | ||
|
6c0283942c | ||
|
d0d948ee05 | ||
|
69b17ee390 | ||
|
697eb33e2c | ||
|
b60989291b | ||
|
f699c4ae94 | ||
|
b6fc5ded9f | ||
|
2ab2c442cf | ||
|
95d34445ed | ||
|
83513ab186 | ||
|
5c8622abc1 | ||
|
4946fc865c | ||
|
d4e89cd38c | ||
|
f0c6a23b8c | ||
|
f7894137b8 | ||
|
9200507034 | ||
|
884a8c0a21 | ||
|
ae26c506f5 | ||
|
f39c9c3645 | ||
|
f5c6a55c0d | ||
|
9b31bb8533 | ||
|
e86e67a7cf | ||
|
0b84893d4a | ||
|
b04eba273a | ||
|
6c43ebbc79 | ||
|
37da8dd118 | ||
|
6329577a6b | ||
|
d57c8b11f3 | ||
|
a9c883a298 | ||
|
f9fea18a92 | ||
|
e84990974b | ||
|
42158f5cca | ||
|
1f53a743c7 | ||
|
eea2c0f734 | ||
|
828d50cea5 | ||
|
c4f435e140 | ||
|
efdf11343c | ||
|
e395798230 | ||
|
4c8028e865 | ||
|
c3a818ef62 | ||
|
f4fe58dcf2 | ||
|
abb7ec0afb | ||
|
ef6627b9f0 | ||
|
5ea682c01f | ||
|
04bc13d73c | ||
|
2852aa2a5b | ||
|
278a862099 | ||
|
5f419f96dd | ||
|
2d1face4e0 | ||
|
7e3a7a5068 | ||
|
c5113b732c | ||
|
1a86938882 | ||
|
cd27e25711 | ||
|
393b4ca495 | ||
|
ea25c7a114 | ||
|
2a6bbd7827 | ||
|
a8f8067315 | ||
|
c552b8b84b | ||
|
ce538cbae2 | ||
|
b8d1fde4e4 | ||
|
2b5239a67f | ||
|
a8c09aaa97 | ||
|
5793e05795 | ||
|
5f7d52f795 | ||
|
84e68332f3 | ||
|
12281a972e | ||
|
8b496dfab5 | ||
|
da69d725d0 | ||
|
e462a256b1 | ||
|
ad8d919409 | ||
|
0151d93b46 | ||
|
8e28d7acca | ||
|
ad0c3a62e1 | ||
|
094f3cd9e5 | ||
|
4962fd01ae | ||
|
b3aafad8f0 | ||
|
b1045e33ac | ||
|
f1ca904c98 | ||
|
a197834f64 | ||
|
8b306c8c76 | ||
|
0c4a66e6a1 | ||
|
aa529c8884 | ||
|
940e653ea6 | ||
|
32c612d5e8 | ||
|
de9a51c863 | ||
|
0099df7f71 | ||
|
fbcadbf71b | ||
|
eeeedce405 | ||
|
8115b37bd9 | ||
|
799943cb47 | ||
|
ee3b81b864 | ||
|
f8bba00868 | ||
|
66a63a8c1f | ||
|
58aed41ff2 | ||
|
06411336c7 | ||
|
6a9749cd42 | ||
|
64dd103e3f | ||
|
bade06f775 | ||
|
4171ba0c9b | ||
|
eef7922de8 | ||
|
175e9a6728 | ||
|
cafe6c75b8 | ||
|
7e8ab9bad7 | ||
|
64c79eea8b | ||
|
5a7d7fe3d0 | ||
|
709c0da81b | ||
|
8b2b4c1a2b | ||
|
8772c03c0f | ||
|
b5deeda251 | ||
|
dcd95d01df | ||
|
dc1f856da1 | ||
|
7c792fba66 | ||
|
45b616fe8c | ||
|
ede356f65e | ||
|
4acb05aa89 | ||
|
420dc651d8 | ||
|
cba4a0783a | ||
|
f5e400962a | ||
|
3ed211ec7a | ||
|
e7b26d48bc | ||
|
07c754c759 | ||
|
a94056e060 | ||
|
f0c7abebb1 | ||
|
da85f9d207 | ||
|
477d566101 | ||
|
4c0e26bfa1 | ||
|
2c4cda7fb3 | ||
|
424d4781ca | ||
|
d93907009c | ||
|
b19217a19b | ||
|
96410eb4f5 | ||
|
bac6cbe8c5 | ||
|
a73707d102 | ||
|
e0a39974e2 | ||
|
c162b349b7 | ||
|
1293a24b59 | ||
|
afa40f4ada | ||
|
6942b5a4f3 | ||
|
db25b1d658 | ||
|
3f3ab23359 | ||
|
da4a2950a7 | ||
|
2bea72dd37 | ||
|
9763353a54 | ||
|
baa976dc6e | ||
|
e43831c078 | ||
|
8c97daddee | ||
|
b162bd047a | ||
|
cc709f242c | ||
|
36de8c9e12 | ||
|
7cbd9525d2 | ||
|
a991a4ba57 | ||
|
fb382a2fea | ||
|
15eece3cf8 | ||
|
9faa4898de | ||
|
5b863848dd | ||
|
4f7abe5226 | ||
|
f1de97dbdd | ||
|
ab73924e53 | ||
|
ed37775e02 | ||
|
b64e07c7d9 | ||
|
7ba3550f8f | ||
|
aa591e3e8e | ||
|
b4ce2913fa | ||
|
32f1677f81 | ||
|
38414f106a | ||
|
4a64979563 | ||
|
1444824911 | ||
|
bdd4a76348 | ||
|
77277a5150 | ||
|
32e9e58e32 | ||
|
d95a53553e | ||
|
374d202daa | ||
|
01b19722d7 | ||
|
063112b138 | ||
|
5c0b206f6e | ||
|
2fc2dcdff6 | ||
|
b3520aabd4 | ||
|
c8b8ddc952 | ||
|
3b3ff3c473 | ||
|
44ae8f557c | ||
|
bb2fedc556 | ||
|
7f7d5171c3 | ||
|
0201b4f71f | ||
|
bd5e04d6e3 | ||
|
3908609918 | ||
|
3630398b17 | ||
|
588fde9fc1 | ||
|
7b78f2c1cd | ||
|
fbceeef47a | ||
|
d804941a18 | ||
|
cc10478f61 | ||
|
4eac64eb59 | ||
|
a75e588cf0 | ||
|
2898eb8c0c | ||
|
eb3997b287 | ||
|
bf8af64f5e | ||
|
f6384291a6 | ||
|
682bc92b45 | ||
|
1f3adf69c6 | ||
|
c75b6091b8 | ||
|
f0a46c4ec9 | ||
|
828e12c345 | ||
|
5ec58a32c2 | ||
|
4b3b9ba207 | ||
|
390e862252 | ||
|
44d4daf599 | ||
|
16b6f95c96 | ||
|
a455a8ad64 | ||
|
77e8e78a88 | ||
|
3207ad13ea | ||
|
6079c91345 | ||
|
b00162470f | ||
|
d55b42eabb | ||
|
cb516fa69a | ||
|
37e54d0f47 | ||
|
5179715a82 | ||
|
2ad8989cd2 | ||
|
60fbaf5e27 | ||
|
9778828863 | ||
|
90e247b091 | ||
|
7b027d00a6 | ||
|
7c8c2b571f | ||
|
6cef4dadac | ||
|
b4e9e95cf2 | ||
|
dae5d782a6 | ||
|
80b77513a6 | ||
|
c9c23a4f9e | ||
|
e60f77df30 | ||
|
5aae305b35 | ||
|
d7f22028aa | ||
|
3a88bd3ddb | ||
|
fa2159f8e8 | ||
|
b234e1dd4e | ||
|
20006621cc | ||
|
512c53c703 | ||
|
8a309f2e6e | ||
|
4f82807b38 | ||
|
7e6d394af0 | ||
|
53f732d480 | ||
|
0d72309324 | ||
|
6f6d036307 | ||
|
a4a9f26e16 | ||
|
ac3e901f19 | ||
|
9d0ea80917 | ||
|
4b69da2d03 | ||
|
2e3d3ab962 | ||
|
e60a0b6d00 | ||
|
d888f25cb6 | ||
|
a1c201d821 | ||
|
eeaefea1e2 | ||
|
600423ad61 | ||
|
4f60ae856b | ||
|
8ebffcfb93 | ||
|
18a3c26447 | ||
|
1aa5127349 | ||
|
84657e85ec | ||
|
5f4f780e16 | ||
|
c710b851c7 | ||
|
7d2a901b6c | ||
|
c1db8ba02d | ||
|
df740e7778 | ||
|
a4b92689bc | ||
|
0e000fe833 | ||
|
e01507a57c | ||
|
22619bb8a9 | ||
|
5537c504cc | ||
|
84889bd5ea | ||
|
2933dd8ad0 | ||
|
a35d17c160 | ||
|
1865ea1be3 | ||
|
5b44b372d1 | ||
|
1bddef857d | ||
|
d64c674394 | ||
|
9d822d4196 | ||
|
91cc64da62 | ||
|
b6e4b2a82e | ||
|
54359d3b06 | ||
|
028531e953 | ||
|
a1f8bae0a3 | ||
|
8479e3d7a3 | ||
|
8fa4613e74 | ||
|
4d518de467 | ||
|
74a561fbed | ||
|
95fb214d16 | ||
|
085643f486 | ||
|
d49afad05c | ||
|
b0a54e1d65 | ||
|
d308796e97 | ||
|
074fc349b0 | ||
|
353b38c80b | ||
|
49296e321e | ||
|
b555158e53 | ||
|
e60f2f147b | ||
|
2d44e85fdc | ||
|
6635ea71e8 | ||
|
802aa8a9e7 | ||
|
ccd86002cf | ||
|
d56b864f83 | ||
|
878609a8e8 | ||
|
f7c7db0b8d | ||
|
bdfa130554 | ||
|
f6e6e6d122 | ||
|
f72f25f8ce | ||
|
d59f306cd9 | ||
|
193e4c9be4 | ||
|
64a51731f7 | ||
|
23ef5daeac | ||
|
eb51631f33 | ||
|
611dd8c60a | ||
|
3494db2a67 | ||
|
f2d3b9eb79 | ||
|
ef5212ce06 | ||
|
511836b7ff | ||
|
bb448be4f4 | ||
|
bb88165d52 | ||
|
a276bacac9 | ||
|
4653781f9b | ||
|
70c227522f | ||
|
c3dc7b4b96 | ||
|
6f7cb49280 | ||
|
38d62c39a6 | ||
|
4f9843ad0d | ||
|
5cea0077e4 | ||
|
8f7676eaa9 | ||
|
7035e5e075 | ||
|
5832aa9bcf | ||
|
091bf99641 | ||
|
2e5c38204a | ||
|
95e376a40b | ||
|
1d21cdcc1e | ||
|
59056c097a | ||
|
8551a65827 | ||
|
0135b40c08 | ||
|
c7e76967c8 | ||
|
50d5932124 | ||
|
f6f1d7f071 | ||
|
a241eb0b69 | ||
|
680eb0e826 | ||
|
f6c0dd965b | ||
|
7c30120dbc | ||
|
7a107f899f | ||
|
d74922e0fd | ||
|
83cd28067a | ||
|
bfa30a94b2 | ||
|
f12451ee5b | ||
|
09c2603f4b | ||
|
56a091a693 | ||
|
7d6244987a | ||
|
b333ecaacc | ||
|
3abf21e053 | ||
|
26e9d482db | ||
|
471b28dc21 | ||
|
c9004f8f3f | ||
|
4e64da2a41 | ||
|
27d9273107 | ||
|
cf4290bf7e | ||
|
df81d15e84 | ||
|
d143398343 | ||
|
732be39b4b | ||
|
4cf6f74dc3 | ||
|
98da6f7057 | ||
|
a294491e0b | ||
|
f502171bdc | ||
|
8cc291f419 | ||
|
228a969b1a | ||
|
698bcdf8c3 | ||
|
cce95f6980 | ||
|
a42de85289 | ||
|
04b7b71a2a | ||
|
3dcad090b3 | ||
|
24e288a3a4 | ||
|
0eadae3ca7 | ||
|
7514e37e5b | ||
|
9f1c6b81a5 | ||
|
f08bf14965 | ||
|
bee73cbb9e | ||
|
d7eb1edeed | ||
|
f1c0052ce7 | ||
|
cddeb87405 | ||
|
04bba78d61 | ||
|
a4faae262b | ||
|
e3cdd85a09 | ||
|
a441eac07a | ||
|
052beaa3ac | ||
|
9cd711ed3e | ||
|
4d0cb1992d | ||
|
e82cf2444b | ||
|
35d1a99592 | ||
|
54cf241044 | ||
|
7631485b0b | ||
|
abb2ab04fb | ||
|
6fd7690961 | ||
|
f1de314e14 | ||
|
0bd90aee34 | ||
|
9963806a62 | ||
|
ee653990e6 | ||
|
e3d6050616 | ||
|
47b5ef9be0 | ||
|
383ee9bd3b | ||
|
024f9dc0aa | ||
|
ece8a26a79 | ||
|
485b28a9ff | ||
|
88a12bc9b9 | ||
|
c3eb636525 | ||
|
0766e70e43 | ||
|
38df95f440 | ||
|
f0feef950b | ||
|
526a81b445 | ||
|
45402964a4 | ||
|
92957ce080 | ||
|
468c70df7d | ||
|
ece9385f22 | ||
|
9ed6d59151 | ||
|
cfcf3c9ba5 | ||
|
7f70979120 | ||
|
e6bdf13407 | ||
|
d371436f01 | ||
|
3955c6ef1b | ||
|
178d130388 | ||
|
43cef6ce6a | ||
|
05bca84294 | ||
|
1cbc5a9b27 | ||
|
2fc89536f4 | ||
|
2025b47cb1 | ||
|
71df5a3198 | ||
|
035a309552 | ||
|
51e735c878 | ||
|
c9e5e94ed2 | ||
|
c477c7703f | ||
|
1c71191aaf | ||
|
1036439f69 | ||
|
680afe25f1 | ||
|
88af8dcd24 | ||
|
eeab36e63f | ||
|
7830fea9ae | ||
|
d5b22a2993 | ||
|
5ff2356e36 | ||
|
671c01aa7f | ||
|
a566da3f87 | ||
|
c73a7b2c9a | ||
|
2d35b097ae | ||
|
8f56cc811d | ||
|
65070524c3 | ||
|
d655643a58 | ||
|
ad36bba13d | ||
|
4d0c8ab2c2 | ||
|
f13069832d | ||
|
d712534c13 | ||
|
15e0f4c963 | ||
|
141e366e22 | ||
|
cd3a546179 | ||
|
7364c6fe44 | ||
|
699c11ca11 | ||
|
331d2a56d0 | ||
|
e550f061f6 | ||
|
d1b2dd47a8 | ||
|
57495db281 | ||
|
e1a3a5fc32 | ||
|
b4e29c5f18 | ||
|
b638b9d808 | ||
|
ef5d2c8a79 | ||
|
a133af8ffb | ||
|
a856251d79 | ||
|
b3bf4d1f8b | ||
|
e019c01fb5 | ||
|
d0ad532fa1 | ||
|
275559585e | ||
|
b27cfd9c36 | ||
|
8a4f87d19e | ||
|
71ec03e5b3 | ||
|
c8cf42ca4e | ||
|
040760ec22 | ||
|
397fd90e94 | ||
|
a51c9be791 | ||
|
9b44d79582 | ||
|
ab844a3f63 | ||
|
3b846b33a8 | ||
|
555cd98958 | ||
|
877630c2b7 | ||
|
00e2c3ff26 | ||
|
4e53440316 | ||
|
e5734e34e9 | ||
|
97f71db21b | ||
|
9fc976d906 | ||
|
761e64df10 | ||
|
4d9ce8c70a | ||
|
c10e80c44b | ||
|
699fa13c8c | ||
|
fcf11f02e5 | ||
|
5262b2904c | ||
|
6d709d52c1 | ||
|
a0681a0cd2 | ||
|
a8dcc589e5 | ||
|
f387766cc2 | ||
|
668e31e5f1 | ||
|
5e1df60d22 | ||
|
e634997791 | ||
|
a751c0c091 | ||
|
9ebab25dfa | ||
|
c2742aa299 | ||
|
dad04e9c8b | ||
|
bfe23298aa | ||
|
825b734b3e | ||
|
26846b86b3 | ||
|
3ba10e2cb5 | ||
|
9480025e61 | ||
|
90b6066252 | ||
|
95d60eed01 | ||
|
0ad6f7e7cb | ||
|
3c74b10043 | ||
|
1870cbb30c | ||
|
82f75d3b9a | ||
|
3a6b6ce5d5 | ||
|
e1693708d4 | ||
|
88a7eef426 | ||
|
fcc398a032 | ||
|
84f1ef33d4 | ||
|
d9f305b6f2 | ||
|
40a13e604f | ||
|
124c5b88d0 | ||
|
419e27d284 | ||
|
8dbd5e7770 | ||
|
e32ce67fd9 | ||
|
b7657e8399 | ||
|
8d1a9e0330 | ||
|
a144b1f9f7 | ||
|
0ffa3f9865 | ||
|
c4fb3768ac | ||
|
778ca7f991 | ||
|
09e16e7aa9 | ||
|
8971b30a76 | ||
|
79fa36fe39 | ||
|
d7357a2173 | ||
|
b2423c56a5 | ||
|
568f744f2e | ||
|
b6c7e863d3 | ||
|
4a9cde8cef | ||
|
27f07a6516 | ||
|
16865d75b7 | ||
|
7e86bcaa92 | ||
|
d1f7ca4239 | ||
|
745e53700f | ||
|
fbbd6452c6 | ||
|
76f5f422b4 | ||
|
c11d1e9cf2 | ||
|
3fd85c92fc | ||
|
17524907f7 | ||
|
9f409ad982 | ||
|
8608d9db40 | ||
|
9180cd3afd | ||
|
f058f3aa33 | ||
|
515ae0ee89 | ||
|
27ee4050c4 | ||
|
d96145e47f | ||
|
25c9946cf3 | ||
|
6738c909a5 | ||
|
a794d58413 | ||
|
3523ac00b8 | ||
|
1fa2dff843 | ||
|
50a5c3e5f7 | ||
|
a4c9927b89 | ||
|
d9db83b9be | ||
|
143a588871 | ||
|
520e51e6ff | ||
|
e58bf8e1ca | ||
|
0ceac67bc6 | ||
|
0ac908603a | ||
|
6c04ec1d89 | ||
|
45d22f2add | ||
|
e24888356f | ||
|
cdf79ba2f9 | ||
|
d4d0b4a6a7 | ||
|
78de4b11db | ||
|
ff308119cf | ||
|
ea77554dc4 | ||
|
92702b80fd | ||
|
4f847ac004 | ||
|
ab2a88c06f | ||
|
9accf494a9 | ||
|
ec3cc120e2 | ||
|
bd0f2139af | ||
|
0478c40ab0 | ||
|
892b725faf | ||
|
2dfca9be7c | ||
|
f717018146 | ||
|
4b83af49b7 | ||
|
19d90f1c21 | ||
|
b14d0d6611 | ||
|
072bc00f64 | ||
|
3f4f12eb1f | ||
|
115cb0e157 | ||
|
fe0b1f55de | ||
|
c7a2ae9b14 | ||
|
22270c168d | ||
|
533a6fcb18 | ||
|
65d2513fde | ||
|
7e11f326ae | ||
|
d2ca0a8c0b | ||
|
e493a9c766 | ||
|
06444ed5c0 | ||
|
42e41f1a7e | ||
|
b2082eda2b | ||
|
e37bee9bf2 | ||
|
aeab2905ad | ||
|
a31feb5365 | ||
|
1194e00dec | ||
|
bbdbb9a050 | ||
|
9bca336876 | ||
|
9c1cde63be | ||
|
c339cc66ac | ||
|
07543ac557 | ||
|
93243f61a1 | ||
|
c9510f7f23 | ||
|
2643f1550f | ||
|
b467a537d5 | ||
|
e8997f7cd9 | ||
|
74975b8411 | ||
|
8b40f781ef | ||
|
bbd5227a1c | ||
|
c1791d7ecb | ||
|
d9aa6e2867 | ||
|
22520f4c7b | ||
|
663b6a1e69 | ||
|
20becbc27b | ||
|
5018c7415a | ||
|
b5dfc9c9ce | ||
|
89e5e36b51 | ||
|
c15ffa2aa2 | ||
|
4b0d773a6e | ||
|
3039d12e3a | ||
|
641dc37b5d | ||
|
1488f0f902 | ||
|
3048412df6 | ||
|
df926ae85a | ||
|
03335d91cf | ||
|
9cd4b974e4 | ||
|
3efcef6229 | ||
|
7fc8727306 | ||
|
446b7e6d44 | ||
|
0285d7b4a7 | ||
|
109b5ad053 | ||
|
b1f3e6d7be | ||
|
6ac8203d40 | ||
|
5a9d2f996a | ||
|
c1f2d12554 | ||
|
24eab4e35a | ||
|
022c10fe0c | ||
|
8bf751d4e3 | ||
|
35828e29e9 | ||
|
237499eced | ||
|
1fbc392800 | ||
|
bb4e42068f | ||
|
0c8b644ee1 | ||
|
56f65c4870 | ||
|
00f56b3809 |
359 changed files with 14751 additions and 123294 deletions
48
.circleci/config.yml
Normal file
48
.circleci/config.yml
Normal file
|
@ -0,0 +1,48 @@
|
|||
version: 2.1
|
||||
orbs:
|
||||
prometheus: prometheus/prometheus@0.17.1
|
||||
executors:
|
||||
# Whenever the Go version is updated here, .promu.yml should also be updated.
|
||||
golang:
|
||||
docker:
|
||||
- image: cimg/go:1.22
|
||||
jobs:
|
||||
test:
|
||||
executor: golang
|
||||
steps:
|
||||
- prometheus/setup_environment
|
||||
- run: make
|
||||
- run: git diff --exit-code
|
||||
- prometheus/store_artifact:
|
||||
file: statsd_exporter
|
||||
workflows:
|
||||
version: 2
|
||||
statsd_exporter:
|
||||
jobs:
|
||||
- test:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- prometheus/build:
|
||||
name: build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- prometheus/publish_master:
|
||||
context: org-context
|
||||
requires:
|
||||
- test
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- prometheus/publish_release:
|
||||
context: org-context
|
||||
requires:
|
||||
- test
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||
branches:
|
||||
ignore: /.*/
|
|
@ -2,3 +2,5 @@
|
|||
.tarballs/
|
||||
|
||||
!.build/linux-amd64
|
||||
!.build/linux-armv7
|
||||
!.build/linux-arm64
|
||||
|
|
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Managing line ending conversions
|
||||
# See http://git-scm.com/docs/gitattributes#_end-of-line_conversion
|
||||
* text=auto
|
||||
* eol=lf
|
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
57
.github/workflows/container_description.yml
vendored
Normal file
57
.github/workflows/container_description.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
name: Push README to Docker Hub
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "README.md"
|
||||
- "README-containers.md"
|
||||
- ".github/workflows/container_description.yml"
|
||||
branches: [ main, master ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
PushDockerHubReadme:
|
||||
runs-on: ubuntu-latest
|
||||
name: Push README to Docker Hub
|
||||
if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks.
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
- name: Set docker hub repo name
|
||||
run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV
|
||||
- name: Push README to Dockerhub
|
||||
uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }}
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
with:
|
||||
destination_container_repo: ${{ env.DOCKER_REPO_NAME }}
|
||||
provider: dockerhub
|
||||
short_description: ${{ env.DOCKER_REPO_NAME }}
|
||||
# Empty string results in README-containers.md being pushed if it
|
||||
# exists. Otherwise, README.md is pushed.
|
||||
readme_file: ''
|
||||
|
||||
PushQuayIoReadme:
|
||||
runs-on: ubuntu-latest
|
||||
name: Push README to quay.io
|
||||
if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks.
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
- name: Set quay.io org name
|
||||
run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV
|
||||
- name: Set quay.io repo name
|
||||
run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV
|
||||
- name: Push README to quay.io
|
||||
uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1
|
||||
env:
|
||||
DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }}
|
||||
with:
|
||||
destination_container_repo: ${{ env.DOCKER_REPO_NAME }}
|
||||
provider: quay
|
||||
# Empty string results in README-containers.md being pushed if it
|
||||
# exists. Otherwise, README.md is pushed.
|
||||
readme_file: ''
|
39
.github/workflows/golangci-lint.yml
vendored
Normal file
39
.github/workflows/golangci-lint.yml
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
# This action is synced from https://github.com/prometheus/prometheus
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "go.sum"
|
||||
- "go.mod"
|
||||
- "**.go"
|
||||
- "scripts/errcheck_excludes.txt"
|
||||
- ".github/workflows/golangci-lint.yml"
|
||||
- ".golangci.yml"
|
||||
pull_request:
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-repo
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
- name: Install snmp_exporter/generator dependencies
|
||||
run: sudo apt-get update && sudo apt-get -y install libsnmp-dev
|
||||
if: github.repository == 'prometheus/snmp_exporter'
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
|
||||
with:
|
||||
args: --verbose
|
||||
version: v1.60.1
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,3 +4,5 @@ dependencies-stamp
|
|||
/.deps
|
||||
/.release
|
||||
/.tarballs
|
||||
*~
|
||||
/vendor
|
||||
|
|
6
.gitpod.Dockerfile
vendored
Normal file
6
.gitpod.Dockerfile
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
FROM gitpod/workspace-full
|
||||
|
||||
RUN sudo apt-get update && \
|
||||
sudo apt-get install -y netcat-traditional socat && \
|
||||
sudo apt-get clean && \
|
||||
sudo rm -rf /var/lib/apt/lists/*
|
13
.gitpod.yml
Normal file
13
.gitpod.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
tasks:
|
||||
- init: go get
|
||||
command: go build . && ./statsd_exporter
|
||||
- command: printf 'Try:\n\n\techo "test.gauge:42|g" | socat - TCP:127.0.0.1:9125\n\n'
|
||||
|
||||
ports:
|
||||
- port: 9102
|
||||
onOpen: open-preview
|
||||
- port: 9125
|
||||
onOpen: ignore
|
||||
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
26
.golangci.yml
Normal file
26
.golangci.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
run:
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
linters:
|
||||
disable:
|
||||
- errcheck
|
||||
enable:
|
||||
- goimports
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- nakedret
|
||||
- staticcheck
|
||||
- unused
|
||||
- whitespace
|
||||
linters-settings:
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
- 'shadow: declaration of "err" shadows declaration at line (\d+)'
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
16
.promu.yml
16
.promu.yml
|
@ -1,14 +1,16 @@
|
|||
go: 1.6.2
|
||||
go:
|
||||
# Whenever the Go version is updated here, .circle/config.yml should also
|
||||
# be updated.
|
||||
version: 1.22
|
||||
repository:
|
||||
path: github.com/prometheus/statsd_exporter
|
||||
build:
|
||||
flags: -a -tags 'netgo static_build'
|
||||
ldflags: |
|
||||
-X {{repoPath}}/vendor/github.com/prometheus/common/version.Version={{.Version}}
|
||||
-X {{repoPath}}/vendor/github.com/prometheus/common/version.Revision={{.Revision}}
|
||||
-X {{repoPath}}/vendor/github.com/prometheus/common/version.Branch={{.Branch}}
|
||||
-X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildUser={{user}}@{{host}}
|
||||
-X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}}
|
||||
-X github.com/prometheus/common/version.Version={{.Version}}
|
||||
-X github.com/prometheus/common/version.Revision={{.Revision}}
|
||||
-X github.com/prometheus/common/version.Branch={{.Branch}}
|
||||
-X github.com/prometheus/common/version.BuildUser={{user}}@{{host}}
|
||||
-X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}}
|
||||
|
||||
tarball:
|
||||
files:
|
||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -1,10 +0,0 @@
|
|||
sudo: false
|
||||
|
||||
language: go
|
||||
go:
|
||||
- 1.6.2
|
||||
- 1.5.4
|
||||
- tip
|
||||
|
||||
script:
|
||||
- make
|
25
.yamllint
Normal file
25
.yamllint
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
extends: default
|
||||
ignore: |
|
||||
ui/react-app/node_modules
|
||||
|
||||
rules:
|
||||
braces:
|
||||
max-spaces-inside: 1
|
||||
level: error
|
||||
brackets:
|
||||
max-spaces-inside: 1
|
||||
level: error
|
||||
commas: disable
|
||||
comments: disable
|
||||
comments-indentation: disable
|
||||
document-start: disable
|
||||
indentation:
|
||||
spaces: consistent
|
||||
indent-sequences: consistent
|
||||
key-duplicates:
|
||||
ignore: |
|
||||
config/testdata/section_key_dup.bad.yml
|
||||
line-length: disable
|
||||
truthy:
|
||||
check-keys: false
|
13
AUTHORS.md
13
AUTHORS.md
|
@ -1,13 +0,0 @@
|
|||
The Prometheus project was started by Matt T. Proud (emeritus) and
|
||||
Julius Volz in 2012.
|
||||
|
||||
Maintainers of this repository:
|
||||
|
||||
* Julius Volz <julius.volz@gmail.com>
|
||||
|
||||
The following individuals have contributed code to this repository
|
||||
(listed in alphabetical order):
|
||||
|
||||
* Björn Rabenstein <beorn@soundcloud.com>
|
||||
* Julius Volz <julius.volz@gmail.com>
|
||||
* Matt T. Proud <matt.proud@gmail.com>
|
471
CHANGELOG.md
471
CHANGELOG.md
|
@ -1,21 +1,470 @@
|
|||
## 0.27.1 / 2024-08-18
|
||||
|
||||
* [FEATURE] Support [dogstatsd extended aggregation](https://github.com/DataDog/datadog-go/blob/master/README.md#extended-aggregation) ([#558](https://github.com/prometheus/statsd_exporter/pull/558))
|
||||
* [SECURITY] Update dependencies
|
||||
|
||||
Thank you [@GrgDev](https://github.com/GrgDev) for the contribution!
|
||||
|
||||
## 0.26.1 / 2024-03-22
|
||||
|
||||
* [SECURITY] Update dependencies, including `google.golang.org/protobuf` for CVE-2024-24786
|
||||
|
||||
This is a maintenance release.
|
||||
|
||||
## 0.26.0 / 2023-12-06
|
||||
|
||||
* [CHANGE] Update dependencies: prometheus/common, client model, and Go 1.21
|
||||
* [FEATURE] Add option to honor original labels from event tags over labels specified in mapping configuration ([#521](https://github.com/prometheus/statsd_exporter/pull/521))
|
||||
|
||||
Thank you @rabenhorst for the `honor_labels` contribution!
|
||||
|
||||
## 0.25.0 / 2023-10-23
|
||||
|
||||
* [CHANGE] Update `client_golang` ([#508](https://github.com/prometheus/statsd_exporter/pull/508), [#513](https://github.com/prometheus/statsd_exporter/pull/513))
|
||||
* [ENHANCEMENT] Process UDP packets asynchronously ([#511](https://github.com/prometheus/statsd_exporter/pull/511))
|
||||
* [BUGFIX] Debug-log incoming lines in cleartext ([#510](https://github.com/prometheus/statsd_exporter/pull/510))
|
||||
* [SECURITY] Update `golang.org/x/net` ([#516](https://github.com/prometheus/statsd_exporter/pull/516))
|
||||
|
||||
This release is less likely to drop UDP packets under very high traffic.
|
||||
Additionally, when it does, it now attempts to record that this happened in the metric `statsd_exporter_udp_packet_drops_total`, where previously this could only be detected from operating system metrics.
|
||||
If you are already monitoring for OS-level UDP packet drops, you _must_ also monitor this metric.
|
||||
The exporter will pull packets from the UDP socket queue much more quickly and queue them internally before processing.
|
||||
Existing monitoring for packet drops will no longer be sufficient to detect dropped events, but attribution to the exporter is easier with this new metric.
|
||||
|
||||
Many thanks to @sumeshpremraj and @kullanici0606 for their contributions, and @pedro-stanaka for helping with the async UDP processing!
|
||||
|
||||
## 0.24.0 / 2023-06-02
|
||||
|
||||
* [FEATURE] Improve the landing page experience ([#504](https://github.com/prometheus/statsd_exporter/pull/504))
|
||||
* [FEATURE] Support scaling parameter in mapping ([#499](https://github.com/prometheus/statsd_exporter/pull/499))
|
||||
|
||||
## 0.23.3 / 2023-06-02
|
||||
|
||||
* [SECURITY] Maintenance release, updating dependencies
|
||||
* [ENHANCEMENT][library] Allow instantiating configuration without going through YAML ([#491](https://github.com/prometheus/statsd_exporter/pull/491))
|
||||
|
||||
Version 0.23.2 was mistagged and thus skipped.
|
||||
|
||||
## 0.23.1 / 2023-03-08
|
||||
|
||||
* [SECURITY] Update all dependencies ([#489](https://github.com/prometheus/statsd_exporter/pull/489))
|
||||
|
||||
## 0.23.0 / 2022-12-07
|
||||
|
||||
* [CHANGE] Print help and version to standard out ([#469](https://github.com/prometheus/statsd_exporter/pull/469))
|
||||
* [FEATURE] Support experimental native histograms ([#474](https://github.com/prometheus/statsd_exporter/pull/474))
|
||||
|
||||
## 0.22.8 / 2022-09-13
|
||||
|
||||
* [BUGFIX] Prevent poisoning with gauge/distribution naming collision ([#461](https://github.com/prometheus/statsd_exporter/pull/461))
|
||||
* [CHANGE] Update `client_golang` dependency ([#463](https://github.com/prometheus/statsd_exporter/pull/463))
|
||||
|
||||
## 0.22.7 / 2022-07-08
|
||||
|
||||
* [CHANGE] Build with Go 1.18 ([#450](https://github.com/prometheus/statsd_exporter/pull/450))
|
||||
|
||||
## 0.22.6 / 2022-07-08
|
||||
|
||||
* [CHANGE] Update dependencies ([#449](https://github.com/prometheus/statsd_exporter/pull/449))
|
||||
|
||||
This is another housekeeping release.
|
||||
|
||||
## 0.22.5 / 2022-05-06
|
||||
|
||||
* [ENHANCEMENT] Add metric for total lines relayed ([#434](https://github.com/prometheus/statsd_exporter/pull/434))
|
||||
|
||||
This release is built with Go 1.17.9, to address security issues in Go.
|
||||
|
||||
## 0.22.4 / 2021-11-26
|
||||
|
||||
* [BUGFIX] Make Docker image compatible with the runAsNonRoot setting in Kubernetes pods ([#409](https://github.com/prometheus/statsd_exporter/pull/409))
|
||||
* [BUGFIX] Library: fix support for custom Registerers with histograms and summaries ([#410](https://github.com/prometheus/statsd_exporter/pull/410))
|
||||
|
||||
## 0.22.3 / 2021-10-26
|
||||
|
||||
* [BUGFIX] Accept metrics with multiple dashes even if not mapped ([#402](https://github.com/prometheus/statsd_exporter/pull/402))
|
||||
|
||||
## 0.22.2 / 2021-09-10
|
||||
|
||||
* [ENHANCEMENT] Add metrics to relay ([#393](https://github.com/prometheus/statsd_exporter/pull/393))
|
||||
|
||||
## 0.22.1 / 2021-09-01
|
||||
|
||||
* [ENHANCEMENT] Accept incoming metrics with multiple dashes (with mapping) ([#381](https://github.com/prometheus/statsd_exporter//pull/381))
|
||||
* [ENHANCEMENT] Allow forwarding messages to statsd for easier transition ([#388](https://github.com/prometheus/statsd_exporter/pull/388))
|
||||
* [BUGFIX] Actually expose pprof endpoints ([#386](https://github.com/prometheus/statsd_exporter/pull/386))
|
||||
* [BUGFIX] Fix performance regression on metric ingestion ([#390](https://github.com/prometheus/statsd_exporter/pull/390))
|
||||
|
||||
## 0.21.0 / 2021-06-10
|
||||
|
||||
* [ENHANCEMENT] Update dependencies & switch to go-kit/log ([#379](https://github.com/prometheus/statsd_exporter/pull/379))
|
||||
|
||||
This release changes the log format to be more structured, in line with other Prometheus projects.
|
||||
|
||||
## 0.20.3 / 2021-06-04
|
||||
|
||||
* [ENHANCEMENT] Use extracted go-kit/log to reduce transitive dependencies ([#378](https://github.com/prometheus/statsd_exporter/pull/378))
|
||||
|
||||
Once again there is no functional change.
|
||||
For library users, the dependency tree shrinks considerably.
|
||||
See [prometheus/common#255](https://github.com/prometheus/common/issues/255) for more details.
|
||||
|
||||
## 0.20.2 / 2021-05-03
|
||||
|
||||
* [BUGFIX] Remove copyleft licensed dependency ([#375](https://github.com/prometheus/statsd_exporter/pull/375))
|
||||
|
||||
There is no functional change for exporter users.
|
||||
Removing this dependency reduces uncertainty for anyone reusing the mapping code.
|
||||
|
||||
## 0.20.1 / 2021-03-26
|
||||
|
||||
* [CHANGE] [library] Split mapper caches out from mapper ([#363](https://github.com/prometheus/statsd_exporter/pull/363))
|
||||
* [BUGFIX] Accept metric segments that start with numbers ([#365](https://github.com/prometheus/statsd_exporter/pull/365))
|
||||
|
||||
## 0.20.0 / 2021-02-05
|
||||
|
||||
* [ENHANCEMENT] Support full defaults for summaries and histograms ([#361](https://github.com/prometheus/statsd_exporter/pull/361))
|
||||
|
||||
This completes support for `summary_options` and `histogram_options`.
|
||||
Change the legacy configuration attributes throughout the mapping configuration as follows:
|
||||
|
||||
* `quantiles: …` to `summary_options: { quantiles: … }`
|
||||
* `buckets: …` to `histogram_options: { buckets: … }`
|
||||
* `timer_type` to `observer_type`.
|
||||
|
||||
Support for the deprecated attributes will be removed in a future release.
|
||||
|
||||
## 0.19.1 / 2021-01-29
|
||||
|
||||
* [BUGFIX] Don't return empty responses to lifecycle api requests ([#360](https://github.com/prometheus/statsd_exporter/pull/360))
|
||||
|
||||
## 0.19.0 / 2021-01-22
|
||||
|
||||
* [CHANGE] [library] Require explicit Registerer ([#347](https://github.com/prometheus/statsd_exporter/pull/347))
|
||||
* [ENHANCEMENT] Add /-/healthy and /-/ready endpoints ([#339](https://github.com/prometheus/statsd_exporter/pull/339))
|
||||
* [BUGFIX] Do not open network ports when only checking config ([#357](https://github.com/prometheus/statsd_exporter/pull/357))
|
||||
|
||||
## 0.18.0 / 2020-08-21
|
||||
|
||||
* [ENHANCEMENT] Allow turning off tagging extensions ([#325](https://github.com/prometheus/statsd_exporter/pull/325))
|
||||
* [ENHANCEMENT] Add a lifecycle API for configuration reloads and restarts ([#329](https://github.com/prometheus/statsd_exporter/pull/329))
|
||||
|
||||
This release changes the interface for the [`github.com/prometheus/statsd_exporter/pkg/line` library package](https://pkg.go.dev/github.com/prometheus/statsd_exporter@v0.18.0/pkg/line?tab=doc) to support the new configurability.
|
||||
|
||||
## 0.17.0 / 2020-06-26
|
||||
|
||||
* [CHANGE] Support non-timer distributions without unit conversion ([#314](https://github.com/prometheus/statsd_exporter/pull/314))
|
||||
* [ENHANCEMENT] Offline configuration check ([#312](https://github.com/prometheus/statsd_exporter/pull/312))
|
||||
* [ENHANCEMENT] Support the SignalFX tagging extension ([#315](https://github.com/prometheus/statsd_exporter/pull/315))
|
||||
* [BUGFIX] Allow matching single-letter metric name components ([#309](https://github.com/prometheus/statsd_exporter/pull/309))
|
||||
|
||||
Distribution and histogram events (type `d`, `h`) are now treated as distinct from timer events (type `ms`).
|
||||
Their values are observed as they are, while timer events are converted from milliseconds to seconds.
|
||||
|
||||
To reflect this generalization, the `observer_type` mapping option replaces `timer_type`.
|
||||
Similary, change `match_metric_type: timer` to `match_metric_type: observer`.
|
||||
The old name remains available for compatibility.
|
||||
|
||||
For users of the mapper library, the `ObserverEvent` replaces `TimerEvent`.
|
||||
For timer metrics, it is emitted by the mapper already converted to seconds.
|
||||
|
||||
## 0.16.0 / 2020-05-29
|
||||
|
||||
* [CHANGE] Break out much of the exporter into reusable packages ([#298](https://github.com/prometheus/statsd_exporter/pull/298))
|
||||
* [ENHANCEMENT] Log ingested lines at debug level ([#305](https://github.com/prometheus/statsd_exporter/pull/305))
|
||||
|
||||
This release mainly consists of an internal reorganization of the exporter.
|
||||
This should not have any impact on users of the binary, if it does, please file
|
||||
an issue.
|
||||
|
||||
For users of the existing library packages, nothing changes.
|
||||
|
||||
There are now multiple new packages available, exposing functionality that had
|
||||
been locked away in the main package. Consider the interfaces of these
|
||||
libraries preliminary; we will change them as we gain experience in how they
|
||||
are used.
|
||||
|
||||
## 0.15.0 / 2020-03-05
|
||||
|
||||
* [ENHANCEMENT] Allow setting granularity for summary metrics ([#290](https://github.com/prometheus/statsd_exporter/pull/290))
|
||||
* [ENHANCEMENT] Support a random-replacement cache invalidation strategy ([#281](https://github.com/prometheus/statsd_exporter/pull/281)
|
||||
|
||||
To facilitate the expanded settings for summaries, the configuration format changes from
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: …
|
||||
timer_type: summary
|
||||
quantiles:
|
||||
- quantile: 0.99
|
||||
error: 0.001
|
||||
- quantile: 0.95
|
||||
error: 0.01
|
||||
…
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: …
|
||||
timer_type: summary
|
||||
summary_options:
|
||||
quantiles:
|
||||
- quantile: 0.99
|
||||
error: 0.001
|
||||
- quantile: 0.95
|
||||
error: 0.01
|
||||
…
|
||||
max_summary_age: 30s
|
||||
summary_age_buckets: 3
|
||||
stream_buffer_size: 1000
|
||||
…
|
||||
```
|
||||
|
||||
For consistency, the format for histogram buckets also changes from
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: …
|
||||
timer_type: histogram
|
||||
buckets: [ 0.01, 0.025, 0.05, 0.1 ]
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: …
|
||||
timer_type: histogram
|
||||
histogram_options:
|
||||
buckets: [ 0.01, 0.025, 0.05, 0.1 ]
|
||||
```
|
||||
|
||||
Transitionally, the old format will still work but is *deprecated*. The new
|
||||
settings are optional.
|
||||
|
||||
For users of the [mapper](https://pkg.go.dev/github.com/prometheus/statsd_exporter/pkg/mapper?tab=doc)
|
||||
as a library, this is a breaking change. To adjust your code, replace
|
||||
`mapping.Buckets` with `mapping.HistogramOptions.Buckets` and
|
||||
`mapping.Quantiles` with `mapping.SummaryOptions.Quantiles`.
|
||||
|
||||
## 0.14.1 / 2020-01-13
|
||||
|
||||
* [BUGFIX] Mapper cache poisoning when name is variable ([#286](https://github.com/prometheus/statsd_exporter/pull/286))
|
||||
* [BUGFIX] nil pointer dereference in UDP listener ([#287](https://github.com/prometheus/statsd_exporter/pull/287))
|
||||
|
||||
Thank you to everyone who reported these, and @bakins for the mapper cache fix!
|
||||
|
||||
## 0.14.0 / 2020-01-10
|
||||
|
||||
* [CHANGE] Switch logging to go-kit ([#283](https://github.com/prometheus/statsd_exporter/pull/283))
|
||||
* [CHANGE] Rename existing metric for mapping cache size ([#284](https://github.com/prometheus/statsd_exporter/pull/284))
|
||||
* [ENHANCEMENT] Add metrics for mapping cache hits ([#280](https://github.com/prometheus/statsd_exporter/pull/280))
|
||||
|
||||
Logs are more structured now. The `fatal` log level no longer exists; use `--log.level=error` instead. The valid log formats are `logfmt` and `json`.
|
||||
|
||||
The metric `statsd_exporter_cache_length` is now called `statsd_metric_mapper_cache_length`.
|
||||
|
||||
## 0.13.0 / 2019-12-06
|
||||
|
||||
* [ENHANCEMENT] Support sampling factors for all statsd metric types ([#264](https://github.com/prometheus/statsd_exporter/issues/250))
|
||||
* [ENHANCEMENT] Support Librato and InfluxDB labeling formats ([#267](https://github.com/prometheus/statsd_exporter/pull/267))
|
||||
|
||||
## 0.12.2 / 2019-07-25
|
||||
|
||||
* [BUGFIX] Fix Unix socket handler ([#252](https://github.com/prometheus/statsd_exporter/pull/252))
|
||||
* [BUGFIX] Fix panic under high load ([#253](https://github.com/prometheus/statsd_exporter/pull/253))
|
||||
|
||||
Thank you to everyone who reported and helped debug these issues!
|
||||
|
||||
## 0.12.1 / 2019-07-08
|
||||
|
||||
* [BUGFIX] Renew TTL when a metric receives updates ([#246](https://github.com/prometheus/statsd_exporter/pull/246))
|
||||
* [CHANGE] Reload on SIGHUP instead of watching the file ([#243](https://github.com/prometheus/statsd_exporter/pull/243))
|
||||
|
||||
## 0.11.2 / 2019-06-14
|
||||
|
||||
* [BUGFIX] Fix TCP handler ([#235](https://github.com/prometheus/statsd_exporter/pull/235))
|
||||
|
||||
## 0.11.1 / 2019-06-14
|
||||
|
||||
* [ENHANCEMENT] Batch event processing for improved ingestion performance ([#227](https://github.com/prometheus/statsd_exporter/pull/227))
|
||||
* [ENHANCEMENT] Switch Prometheus client to promhttp, freeing the standard HTTP metrics ([#233](https://github.com/prometheus/statsd_exporter/pull/233))
|
||||
|
||||
With #233, the exporter no longer exports metrics about its own HTTP status. These were not helpful since you could not get them when scraping fails. This allows mapping to metric names like `http_requests_total` that are useful as application metrics.
|
||||
|
||||
## 0.10.6 / 2019-06-07
|
||||
|
||||
* [BUGFIX] Fix mapping collision for metrics with different types, but the same name ([#229](https://github.com/prometheus/statsd_exporter/pull/229))
|
||||
|
||||
## 0.10.5 / 2019-05-27
|
||||
|
||||
* [BUGFIX] Fix "Error: inconsistent label cardinality: expected 0 label values but got N in prometheus.Labels" ([#224](https://github.com/prometheus/statsd_exporter/pull/224))
|
||||
|
||||
## 0.10.4 / 2019-05-20
|
||||
|
||||
* [BUGFIX] Revert #218 due to a race condition ([#221](https://github.com/prometheus/statsd_exporter/pull/221))
|
||||
|
||||
## 0.10.3 / 2019-05-17
|
||||
|
||||
* [ENHANCEMENT] Reduce allocations when escaping metric names ([#217](https://github.com/prometheus/statsd_exporter/pull/217))
|
||||
* [ENHANCEMENT] Reduce allocations when handling packets ([#218](https://github.com/prometheus/statsd_exporter/pull/218))
|
||||
* [ENHANCEMENT] Optimize label sorting ([#219](https://github.com/prometheus/statsd_exporter/pull/219))
|
||||
|
||||
This release is entirely powered by @claytono. Kudos!
|
||||
|
||||
## 0.10.2 / 2019-05-17
|
||||
|
||||
* [CHANGE] Do not run as root in the Docker container by default ([#202](https://github.com/prometheus/statsd_exporter/pull/202))
|
||||
* [FEATURE] Add metric for count of events by action ([#193](https://github.com/prometheus/statsd_exporter/pull/193))
|
||||
* [FEATURE] Add metric for count of distinct metric names ([#200](https://github.com/prometheus/statsd_exporter/pull/200))
|
||||
* [FEATURE] Add UNIX socket listener support ([#199](https://github.com/prometheus/statsd_exporter/pull/199))
|
||||
* [FEATURE] Accept Datadog [distributions](https://docs.datadoghq.com/graphing/metrics/distributions/) ([#211](https://github.com/prometheus/statsd_exporter/pull/211))
|
||||
* [ENHANCEMENT] Add a health check to the Docker container ([#182](https://github.com/prometheus/statsd_exporter/pull/182))
|
||||
* [ENHANCEMENT] Allow inconsistent label sets ([#194](https://github.com/prometheus/statsd_exporter/pull/194))
|
||||
* [ENHANCEMENT] Speed up sanitization of metric names ([#197](https://github.com/prometheus/statsd_exporter/pull/197))
|
||||
* [ENHANCEMENT] Enable pprof endpoints ([#205](https://github.com/prometheus/statsd_exporter/pull/205))
|
||||
* [ENHANCEMENT] DogStatsD tag parsing is faster ([#210](https://github.com/prometheus/statsd_exporter/pull/210))
|
||||
* [ENHANCEMENT] Cache mapped metrics ([#198](https://github.com/prometheus/statsd_exporter/pull/198))
|
||||
* [BUGFIX] Fix panic if a mapping resulted in an empty name ([#192](https://github.com/prometheus/statsd_exporter/pull/192))
|
||||
* [BUGFIX] Ensure that there are always default quantiles if using summaries ([#212](https://github.com/prometheus/statsd_exporter/pull/212))
|
||||
* [BUGFIX] Prevent ingesting conflicting metric types that would make scraping fail ([#213](https://github.com/prometheus/statsd_exporter/pull/213))
|
||||
|
||||
With #192, the count of events rejected because of negative counter increments has moved into the `statsd_exporter_events_error_total` metric, instead of being lumped in with the different kinds of successful events.
|
||||
|
||||
## 0.9.0 / 2019-03-11
|
||||
|
||||
* [ENHANCEMENT] Update the Prometheus client library to 0.9.2 ([#171](https://github.com/prometheus/statsd_exporter/pull/171))
|
||||
* [FEATURE] Metrics can now be expired with a per-mapping TTL ([#164](https://github.com/prometheus/statsd_exporter/pull/164))
|
||||
* [CHANGE] Timers that mapped to a summary are scaled to seconds, just like histograms ([#178](https://github.com/prometheus/statsd_exporter/pull/178))
|
||||
|
||||
If you are using summaries, all your quantiles and `_total` will change by a factor of 1000.
|
||||
Adjust your queries and dashboards, or consider switching to histograms altogether.
|
||||
|
||||
## 0.8.1 / 2018-12-05
|
||||
|
||||
* [BUGFIX] Expose the counter for unmapped matches ([#161](https://github.com/prometheus/statsd_exporter/pull/161))
|
||||
* [BUGFIX] Unsuccessful backtracking does not clobber captures ([#169](https://github.com/prometheus/statsd_exporter/pull/169), fixes [#168](https://github.com/prometheus/statsd_exporter/issues/168))
|
||||
|
||||
## 0.8.0 / 2018-10-12
|
||||
|
||||
* [ENHANCEMENT] Speed up glob matching ([#157](https://github.com/prometheus/statsd_exporter/pull/157))
|
||||
|
||||
This release replaces the implementation of the glob matching mechanism,
|
||||
speeding it up significantly. In certain sub-optimal configurations, a warning
|
||||
is logged.
|
||||
|
||||
This major enhancement was contributed by [Wangchong Zhou](https://github.com/fffonion).
|
||||
|
||||
## 0.7.0 / 2018-08-22
|
||||
|
||||
This is a breaking release, but the migration is easy: command line flags now
|
||||
require two dashes (`--help` instead of `-help`). The previous flag library
|
||||
already accepts this form, so if necessary you can migrate the flags first
|
||||
before upgrading.
|
||||
|
||||
The deprecated `--statsd.listen-address` flag has been removed, use
|
||||
`--statsd.listen-udp` instead.
|
||||
|
||||
* [CHANGE] Switch to Kingpin for flags, fixes setting log level ([#141](https://github.com/prometheus/statsd_exporter/pull/141))
|
||||
* [ENHANCEMENT] Allow matching on specific metric types ([#136](https://github.com/prometheus/statsd_exporter/pulls/136))
|
||||
* [ENHANCEMENT] Summary quantiles can be configured ([#135](https://github.com/prometheus/statsd_exporter/pulls/135))
|
||||
* [BUGFIX] Fix panic if an invalid regular expression is supplied ([#126](https://github.com/prometheus/statsd_exporter/pulls/126))
|
||||
|
||||
## 0.6.0 / 2018-01-17
|
||||
|
||||
* [ENHANCEMENT] Add a drop action ([#115](https://github.com/prometheus/statsd_exporter/pulls/115))
|
||||
* [ENHANCEMENT] Allow templating metric names ([#117](https://github.com/prometheus/statsd_exporter/pulls/117))
|
||||
|
||||
## 0.5.0 / 2017-11-16
|
||||
|
||||
NOTE: This release breaks backward compatibility. `statsd_exporter` now uses
|
||||
a YAML configuration file. You must convert your mappings configuration to
|
||||
the new format when you upgrade. For example, the configuration
|
||||
|
||||
```
|
||||
test.dispatcher.*.*.*
|
||||
name="dispatcher_events_total"
|
||||
processor="$1"
|
||||
action="$2"
|
||||
outcome="$3"
|
||||
job="test_dispatcher"
|
||||
|
||||
*.signup.*.*
|
||||
name="signup_events_total"
|
||||
provider="$2"
|
||||
outcome="$3"
|
||||
job="${1}_server"
|
||||
```
|
||||
|
||||
now has the format
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: test.dispatcher.*.*.*
|
||||
help: "The total number of events handled by the dispatcher."
|
||||
name: "dispatcher_events_total"
|
||||
labels:
|
||||
processor: "$1"
|
||||
action: "$2"
|
||||
outcome: "$3"
|
||||
job: "test_dispatcher"
|
||||
- match: *.signup.*.*
|
||||
name: "signup_events_total"
|
||||
help: "The total number of signup events."
|
||||
labels:
|
||||
provider: "$2"
|
||||
outcome: "$3"
|
||||
job: "${1}_server"
|
||||
```
|
||||
|
||||
The help field is optional.
|
||||
|
||||
There is a [tool](https://github.com/bakins/statsd-exporter-convert) available to help with this conversion.
|
||||
|
||||
* [CHANGE] Replace the overloaded "packets" metric ([#106](https://github.com/prometheus/statsd_exporter/pulls/106))
|
||||
* [CHANGE] Removed `-statsd.add-suffix` option flag [#99](https://github.com/prometheus/statsd_exporter/pulls/99). Users should remove
|
||||
this flag when upgrading. Metrics will no longer automatically include the
|
||||
suffixes `_timer` or `counter`. You may need to adjust any graphs that used
|
||||
metrics with these suffixes.
|
||||
* [CHANGE] Reduce log levels [#92](https://github.com/prometheus/statsd_exporter/pulls/92). Many log events have been changed from error
|
||||
to debug log level.
|
||||
* [CHANGE] Use YAML for configuration file [#66](https://github.com/prometheus/statsd_exporter/pulls/66). See note above about file format
|
||||
conversion.
|
||||
* [ENHANCEMENT] Allow help text to be customized [#87](https://github.com/prometheus/statsd_exporter/pulls/87)
|
||||
* [ENHANCEMENT] Add support for regex mappers [#85](https://github.com/prometheus/statsd_exporter/pulls/85)
|
||||
* [ENHANCEMENT] Add TCP listener support [#71](https://github.com/prometheus/statsd_exporter/pulls/71)
|
||||
* [ENHANCEMENT] Allow histograms for timer metrics [#66](https://github.com/prometheus/statsd_exporter/pulls/66)
|
||||
* [ENHANCEMENT] Added support for sampling factor on timing events [#28](https://github.com/prometheus/statsd_exporter/pulls/28)
|
||||
* [BUGFIX] Conflicting label sets no longer crash the exporter and will be
|
||||
ignored. Restart to clear the remembered label set. [#72](https://github.com/prometheus/statsd_exporter/pulls/72)
|
||||
|
||||
## 0.4.0 / 2017-05-12
|
||||
|
||||
* [ENHANCEMENT] Improve mapping configuration parser [#61](https://github.com/prometheus/statsd_exporter/pulls/61)
|
||||
* [ENHANCEMENT] Add increment/decrement support to Gauges [#65](https://github.com/prometheus/statsd_exporter/pulls/65)
|
||||
* [BUGFIX] Tolerate more forms of broken lines from StatsD [#48](https://github.com/prometheus/statsd_exporter/pulls/48)
|
||||
* [BUGFIX] Skip metrics with invalid utf8 [#50](https://github.com/prometheus/statsd_exporter/pulls/50)
|
||||
* [BUGFIX] ListenAndServe now fails on exit [#58](https://github.com/prometheus/statsd_exporter/pulls/58)
|
||||
|
||||
## 0.3.0 / 2016-05-05
|
||||
|
||||
* [CHANGE] Drop `_count` suffix for `loaded_mappings` metric (#41)
|
||||
* [IMPROVEMENT] Use common's log and version packages, and add -version flag (#44)
|
||||
* [IMPROVEMENT] Add flag to disable metric type suffixes (#37)
|
||||
* [BUGFIX] Increase receivable UDP datagram size to 65535 bytes (#36)
|
||||
* [BUGFIX] Warn, not panic when negative number counter is submitted (#33)
|
||||
* [CHANGE] Drop `_count` suffix for `loaded_mappings` metric ([#41](https://github.com/prometheus/statsd_exporter/pulls/41))
|
||||
* [ENHANCEMENT] Use common's log and version packages, and add -version flag ([#44](https://github.com/prometheus/statsd_exporter/pulls/44))
|
||||
* [ENHANCEMENT] Add flag to disable metric type suffixes ([#37](https://github.com/prometheus/statsd_exporter/pulls/37))
|
||||
* [BUGFIX] Increase receivable UDP datagram size to 65535 bytes ([#36](https://github.com/prometheus/statsd_exporter/pulls/36))
|
||||
* [BUGFIX] Warn, not panic when negative number counter is submitted ([#33](https://github.com/prometheus/statsd_exporter/pulls/33))
|
||||
|
||||
## 0.2.0 / 2016-03-19
|
||||
|
||||
NOTE: This release renames `statsd_bridge` to `statsd_exporter`
|
||||
|
||||
* [CHANGE] New Dockerfile using alpine-golang-make-onbuild base image (#17)
|
||||
* [IMPROVEMENT] Allow configuration of UDP read buffer (#22)
|
||||
* [BUGFIX] allow metrics with dashes when mapping (#24)
|
||||
* [IMPROVEMENT] add root endpoint with redirect (#25)
|
||||
* [CHANGE] rename bridge to exporter (#26)
|
||||
|
||||
* [CHANGE] New Dockerfile using alpine-golang-make-onbuild base image ([#17](https://github.com/prometheus/statsd_exporter/pulls/17))
|
||||
* [ENHANCEMENT] Allow configuration of UDP read buffer ([#22](https://github.com/prometheus/statsd_exporter/pulls/22))
|
||||
* [BUGFIX] allow metrics with dashes when mapping ([#24](https://github.com/prometheus/statsd_exporter/pulls/24))
|
||||
* [ENHANCEMENT] add root endpoint with redirect ([#25](https://github.com/prometheus/statsd_exporter/pulls/25))
|
||||
* [CHANGE] rename bridge to exporter ([#26](https://github.com/prometheus/statsd_exporter/pulls/26))
|
||||
|
||||
## 0.1.0 / 2015-04-17
|
||||
|
||||
|
|
3
CODE_OF_CONDUCT.md
Normal file
3
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Prometheus Community Code of Conduct
|
||||
|
||||
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
Prometheus uses GitHub to manage reviews of pull requests.
|
||||
|
||||
* If you have a trivial fix or improvement, go ahead and create a pull
|
||||
request, addressing (with `@...`) one or more of the maintainers
|
||||
(see [AUTHORS.md](AUTHORS.md)) in the description of the pull request.
|
||||
* If you have a trivial fix or improvement, go ahead and create a pull request,
|
||||
addressing (with `@...`) the maintainer of this repository (see
|
||||
[MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request.
|
||||
|
||||
* If you plan to do something more involved, first discuss your ideas
|
||||
on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers).
|
||||
|
@ -16,3 +16,5 @@ Prometheus uses GitHub to manage reviews of pull requests.
|
|||
and the _Formatting and style_ section of Peter Bourgon's [Go: Best
|
||||
Practices for Production
|
||||
Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style).
|
||||
|
||||
* [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/prometheus/statsd_exporter)
|
||||
|
|
14
Dockerfile
14
Dockerfile
|
@ -1,7 +1,13 @@
|
|||
FROM quay.io/prometheus/busybox:latest
|
||||
MAINTAINER The Prometheus Authors <prometheus-developers@googlegroups.com>
|
||||
ARG ARCH="amd64"
|
||||
ARG OS="linux"
|
||||
FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest
|
||||
LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
|
||||
|
||||
COPY statsd_exporter /bin/statsd_exporter
|
||||
ARG ARCH="amd64"
|
||||
ARG OS="linux"
|
||||
COPY .build/${OS}-${ARCH}/statsd_exporter /bin/statsd_exporter
|
||||
|
||||
EXPOSE 9102 9125/udp
|
||||
USER 65534
|
||||
EXPOSE 9102 9125 9125/udp
|
||||
HEALTHCHECK CMD wget --spider -S "http://localhost:9102/metrics" -T 60 2>&1 || exit 1
|
||||
ENTRYPOINT [ "/bin/statsd_exporter" ]
|
||||
|
|
1
MAINTAINERS.md
Normal file
1
MAINTAINERS.md
Normal file
|
@ -0,0 +1 @@
|
|||
* Matthias Rampke <matthias@prometheus.io>
|
54
Makefile
54
Makefile
|
@ -11,50 +11,18 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
GO := GO15VENDOREXPERIMENT=1 go
|
||||
PROMU := $(GOPATH)/bin/promu
|
||||
pkgs = $(shell $(GO) list ./... | grep -v /vendor/)
|
||||
# Needs to be defined before including Makefile.common to auto-generate targets
|
||||
DOCKER_ARCHS ?= amd64 armv7 arm64
|
||||
|
||||
include Makefile.common
|
||||
|
||||
STATICCHECK_IGNORE =
|
||||
|
||||
PREFIX ?= $(shell pwd)
|
||||
BIN_DIR ?= $(shell pwd)
|
||||
DOCKER_IMAGE_NAME ?= statsd-exporter
|
||||
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
|
||||
|
||||
.PHONY: bench
|
||||
bench:
|
||||
@echo ">> running all benchmarks"
|
||||
$(GO) test -bench . -race $(pkgs)
|
||||
|
||||
all: format build test
|
||||
|
||||
style:
|
||||
@echo ">> checking code style"
|
||||
@! gofmt -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^'
|
||||
|
||||
test:
|
||||
@echo ">> running tests"
|
||||
@$(GO) test -short $(pkgs)
|
||||
|
||||
format:
|
||||
@echo ">> formatting code"
|
||||
@$(GO) fmt $(pkgs)
|
||||
|
||||
vet:
|
||||
@echo ">> vetting code"
|
||||
@$(GO) vet $(pkgs)
|
||||
|
||||
build: promu
|
||||
@echo ">> building binaries"
|
||||
@$(PROMU) build --prefix $(PREFIX)
|
||||
|
||||
tarball: promu
|
||||
@echo ">> building release tarball"
|
||||
@$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
|
||||
|
||||
docker:
|
||||
@echo ">> building docker image"
|
||||
@docker build -t "$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" .
|
||||
|
||||
promu:
|
||||
@GOOS=$(shell uname -s | tr A-Z a-z) \
|
||||
GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \
|
||||
$(GO) get -u github.com/prometheus/promu
|
||||
|
||||
|
||||
.PHONY: all style format build test vet tarball docker promu
|
||||
all: bench
|
||||
|
|
131
Makefile.COMMON
131
Makefile.COMMON
|
@ -1,131 +0,0 @@
|
|||
# Copyright 2015 The Prometheus Authors
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# THE AUTHORITATIVE VERSION OF THIS MAKEFILE LIVES IN:
|
||||
#
|
||||
# https://github.com/prometheus/utils
|
||||
#
|
||||
# PLEASE MAKE ANY CHANGES THERE AND PROPAGATE THEM TO ALL PROMETHEUS
|
||||
# REPOSITORIES THAT ARE USING THIS MAKEFILE.
|
||||
#
|
||||
# This file provides common Makefile infrastructure for several Prometheus
|
||||
# components. This includes make tasks for downloading Go, setting up a
|
||||
# self-contained build environment, fetching Go dependencies, building
|
||||
# binaries, running tests, and doing release management. This file is intended
|
||||
# to be included from a project's Makefile, which needs to define the following
|
||||
# variables, at a minimum:
|
||||
#
|
||||
# * VERSION - The current version of the project in question.
|
||||
# * TARGET - The desired name of the built binary.
|
||||
#
|
||||
# Many of the variables defined below are defined conditionally (using '?'),
|
||||
# which allows the project's main Makefile to override any of these settings, if
|
||||
# needed. See also:
|
||||
#
|
||||
# https://www.gnu.org/software/make/manual/html_node/Flavors.html#Flavors.
|
||||
#
|
||||
# The including Makefile may define any number of extra targets that are
|
||||
# specific to that project.
|
||||
|
||||
VERSION ?= $(error VERSION not set in including Makefile)
|
||||
TARGET ?= $(error TARGET not set in including Makefile)
|
||||
|
||||
SRC ?= $(shell find . -type f -name "*.go" ! -path "./.build/*")
|
||||
GOOS ?= $(shell uname | tr A-Z a-z)
|
||||
GOARCH ?= $(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m)))
|
||||
|
||||
GO_VERSION ?= 1.5.3
|
||||
GOPATH ?= $(CURDIR)/.build/gopath
|
||||
ROOTPKG ?= github.com/prometheus/$(TARGET)
|
||||
SELFLINK ?= $(GOPATH)/src/$(ROOTPKG)
|
||||
|
||||
# Check for the correct version of go in the path. If we find it, use it.
|
||||
# Otherwise, prepare to build go locally.
|
||||
ifeq ($(shell command -v "go" >/dev/null && go version | sed -e 's/^[^0-9.]*\([0-9.]*\).*/\1/'), $(GO_VERSION))
|
||||
GOCC ?= $(shell command -v "go")
|
||||
GOFMT ?= $(shell command -v "gofmt")
|
||||
GO ?= GOPATH=$(GOPATH) $(GOCC)
|
||||
else
|
||||
GOURL ?= https://golang.org/dl
|
||||
GOPKG ?= go$(GO_VERSION).$(GOOS)-$(GOARCH).tar.gz
|
||||
GOROOT ?= $(CURDIR)/.build/go$(GO_VERSION)
|
||||
GOCC ?= $(GOROOT)/bin/go
|
||||
GOFMT ?= $(GOROOT)/bin/gofmt
|
||||
GO ?= GOPATH=$(GOPATH) GOROOT=$(GOROOT) $(GOCC)
|
||||
endif
|
||||
|
||||
# Use vendored dependencies if available. Otherwise try to download them.
|
||||
ifneq (,$(wildcard vendor))
|
||||
DEPENDENCIES := $(shell find vendor/ -type f -iname '*.go')
|
||||
GO := GO15VENDOREXPERIMENT=1 $(GO)
|
||||
else
|
||||
DEPENDENCIES := dependencies-stamp
|
||||
endif
|
||||
|
||||
# Never honor GOBIN, should it be set at all.
|
||||
unexport GOBIN
|
||||
|
||||
SUFFIX ?= $(GOOS)-$(GOARCH)
|
||||
BINARY ?= $(TARGET)
|
||||
ARCHIVE ?= $(TARGET)-$(VERSION).$(SUFFIX).tar.gz
|
||||
|
||||
default: $(BINARY)
|
||||
|
||||
$(BINARY): $(GOCC) $(SRC) $(DEPENDENCIES) Makefile Makefile.COMMON | $(SELFLINK)
|
||||
cd $(SELFLINK) && $(GO) build $(GOFLAGS) -o $@
|
||||
|
||||
.PHONY: archive
|
||||
archive: $(ARCHIVE)
|
||||
|
||||
$(ARCHIVE): $(BINARY)
|
||||
tar -czf $@ $<
|
||||
|
||||
.PHONY: tag
|
||||
tag:
|
||||
git tag $(VERSION)
|
||||
git push --tags
|
||||
|
||||
.PHONY: test
|
||||
test: $(GOCC) $(DEPENDENCIES) | $(SELFLINK)
|
||||
cd $(SELFLINK) && $(GO) test $$($(GO) list ./... | grep -v /vendor/)
|
||||
|
||||
.PHONY: format
|
||||
format: $(GOCC)
|
||||
find . -iname '*.go' | egrep -v "^\./\.build|./generated|\./vendor|\.(l|y)\.go" | xargs -n1 $(GOFMT) -w -s=true
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BINARY) $(ARCHIVE) .build *-stamp
|
||||
|
||||
|
||||
|
||||
$(GOCC):
|
||||
@echo Go version $(GO_VERSION) required but not found in PATH.
|
||||
@echo About to download and install go$(GO_VERSION) to $(GOROOT)
|
||||
@echo Abort now if you want to manually install it system-wide instead.
|
||||
@echo
|
||||
@sleep 5
|
||||
mkdir -p .build
|
||||
# The archive contains a single directory called 'go/'.
|
||||
curl -L $(GOURL)/$(GOPKG) | tar -C .build -xzf -
|
||||
rm -rf $(GOROOT)
|
||||
mv .build/go $(GOROOT)
|
||||
|
||||
$(SELFLINK):
|
||||
mkdir -p $(dir $@)
|
||||
ln -s $(CURDIR) $@
|
||||
|
||||
# Download dependencies if project doesn't vendor them.
|
||||
dependencies-stamp: $(GOCC) $(SRC) | $(SELFLINK)
|
||||
$(GO) get -d
|
||||
touch $@
|
277
Makefile.common
Normal file
277
Makefile.common
Normal file
|
@ -0,0 +1,277 @@
|
|||
# Copyright 2018 The Prometheus Authors
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# A common Makefile that includes rules to be reused in different prometheus projects.
|
||||
# !!! Open PRs only against the prometheus/prometheus/Makefile.common repository!
|
||||
|
||||
# Example usage :
|
||||
# Create the main Makefile in the root project directory.
|
||||
# include Makefile.common
|
||||
# customTarget:
|
||||
# @echo ">> Running customTarget"
|
||||
#
|
||||
|
||||
# Ensure GOBIN is not set during build so that promu is installed to the correct path
|
||||
unexport GOBIN
|
||||
|
||||
GO ?= go
|
||||
GOFMT ?= $(GO)fmt
|
||||
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
|
||||
GOOPTS ?=
|
||||
GOHOSTOS ?= $(shell $(GO) env GOHOSTOS)
|
||||
GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH)
|
||||
|
||||
GO_VERSION ?= $(shell $(GO) version)
|
||||
GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))
|
||||
PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.')
|
||||
|
||||
PROMU := $(FIRST_GOPATH)/bin/promu
|
||||
pkgs = ./...
|
||||
|
||||
ifeq (arm, $(GOHOSTARCH))
|
||||
GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM)
|
||||
GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM)
|
||||
else
|
||||
GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
|
||||
endif
|
||||
|
||||
GOTEST := $(GO) test
|
||||
GOTEST_DIR :=
|
||||
ifneq ($(CIRCLE_JOB),)
|
||||
ifneq ($(shell command -v gotestsum 2> /dev/null),)
|
||||
GOTEST_DIR := test-results
|
||||
GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
|
||||
endif
|
||||
endif
|
||||
|
||||
PROMU_VERSION ?= 0.17.0
|
||||
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
|
||||
|
||||
SKIP_GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT_OPTS ?=
|
||||
GOLANGCI_LINT_VERSION ?= v1.60.1
|
||||
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
|
||||
# windows isn't included here because of the path separator being different.
|
||||
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
|
||||
ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64))
|
||||
# If we're in CI and there is an Actions file, that means the linter
|
||||
# is being run in Actions, so we don't need to run it here.
|
||||
ifneq (,$(SKIP_GOLANGCI_LINT))
|
||||
GOLANGCI_LINT :=
|
||||
else ifeq (,$(CIRCLE_JOB))
|
||||
GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint
|
||||
else ifeq (,$(wildcard .github/workflows/golangci-lint.yml))
|
||||
GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
PREFIX ?= $(shell pwd)
|
||||
BIN_DIR ?= $(shell pwd)
|
||||
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
|
||||
DOCKERFILE_PATH ?= ./Dockerfile
|
||||
DOCKERBUILD_CONTEXT ?= ./
|
||||
DOCKER_REPO ?= prom
|
||||
|
||||
DOCKER_ARCHS ?= amd64
|
||||
|
||||
BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS))
|
||||
PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS))
|
||||
TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS))
|
||||
|
||||
SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG))
|
||||
|
||||
ifeq ($(GOHOSTARCH),amd64)
|
||||
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows))
|
||||
# Only supported on amd64
|
||||
test-flags := -race
|
||||
endif
|
||||
endif
|
||||
|
||||
# This rule is used to forward a target like "build" to "common-build". This
|
||||
# allows a new "build" target to be defined in a Makefile which includes this
|
||||
# one and override "common-build" without override warnings.
|
||||
%: common-% ;
|
||||
|
||||
.PHONY: common-all
|
||||
common-all: precheck style check_license lint yamllint unused build test
|
||||
|
||||
.PHONY: common-style
|
||||
common-style:
|
||||
@echo ">> checking code style"
|
||||
@fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
|
||||
if [ -n "$${fmtRes}" ]; then \
|
||||
echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
|
||||
echo "Please ensure you are using $$($(GO) version) for formatting code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: common-check_license
|
||||
common-check_license:
|
||||
@echo ">> checking license header"
|
||||
@licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
|
||||
awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
|
||||
done); \
|
||||
if [ -n "$${licRes}" ]; then \
|
||||
echo "license header checking failed:"; echo "$${licRes}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: common-deps
|
||||
common-deps:
|
||||
@echo ">> getting dependencies"
|
||||
$(GO) mod download
|
||||
|
||||
.PHONY: update-go-deps
|
||||
update-go-deps:
|
||||
@echo ">> updating Go dependencies"
|
||||
@for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \
|
||||
$(GO) get -d $$m; \
|
||||
done
|
||||
$(GO) mod tidy
|
||||
|
||||
.PHONY: common-test-short
|
||||
common-test-short: $(GOTEST_DIR)
|
||||
@echo ">> running short tests"
|
||||
$(GOTEST) -short $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-test
|
||||
common-test: $(GOTEST_DIR)
|
||||
@echo ">> running all tests"
|
||||
$(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
|
||||
|
||||
$(GOTEST_DIR):
|
||||
@mkdir -p $@
|
||||
|
||||
.PHONY: common-format
|
||||
common-format:
|
||||
@echo ">> formatting code"
|
||||
$(GO) fmt $(pkgs)
|
||||
|
||||
.PHONY: common-vet
|
||||
common-vet:
|
||||
@echo ">> vetting code"
|
||||
$(GO) vet $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-lint
|
||||
common-lint: $(GOLANGCI_LINT)
|
||||
ifdef GOLANGCI_LINT
|
||||
@echo ">> running golangci-lint"
|
||||
$(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
|
||||
endif
|
||||
|
||||
.PHONY: common-lint-fix
|
||||
common-lint-fix: $(GOLANGCI_LINT)
|
||||
ifdef GOLANGCI_LINT
|
||||
@echo ">> running golangci-lint fix"
|
||||
$(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs)
|
||||
endif
|
||||
|
||||
.PHONY: common-yamllint
|
||||
common-yamllint:
|
||||
@echo ">> running yamllint on all YAML files in the repository"
|
||||
ifeq (, $(shell command -v yamllint 2> /dev/null))
|
||||
@echo "yamllint not installed so skipping"
|
||||
else
|
||||
yamllint .
|
||||
endif
|
||||
|
||||
# For backward-compatibility.
|
||||
.PHONY: common-staticcheck
|
||||
common-staticcheck: lint
|
||||
|
||||
.PHONY: common-unused
|
||||
common-unused:
|
||||
@echo ">> running check for unused/missing packages in go.mod"
|
||||
$(GO) mod tidy
|
||||
@git diff --exit-code -- go.sum go.mod
|
||||
|
||||
.PHONY: common-build
|
||||
common-build: promu
|
||||
@echo ">> building binaries"
|
||||
$(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
|
||||
|
||||
.PHONY: common-tarball
|
||||
common-tarball: promu
|
||||
@echo ">> building release tarball"
|
||||
$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
|
||||
|
||||
.PHONY: common-docker-repo-name
|
||||
common-docker-repo-name:
|
||||
@echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)"
|
||||
|
||||
.PHONY: common-docker $(BUILD_DOCKER_ARCHS)
|
||||
common-docker: $(BUILD_DOCKER_ARCHS)
|
||||
$(BUILD_DOCKER_ARCHS): common-docker-%:
|
||||
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \
|
||||
-f $(DOCKERFILE_PATH) \
|
||||
--build-arg ARCH="$*" \
|
||||
--build-arg OS="linux" \
|
||||
$(DOCKERBUILD_CONTEXT)
|
||||
|
||||
.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
|
||||
common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
|
||||
$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)"
|
||||
|
||||
DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
|
||||
.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
|
||||
common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
|
||||
$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
|
||||
|
||||
.PHONY: common-docker-manifest
|
||||
common-docker-manifest:
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG))
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)"
|
||||
|
||||
.PHONY: promu
|
||||
promu: $(PROMU)
|
||||
|
||||
$(PROMU):
|
||||
$(eval PROMU_TMP := $(shell mktemp -d))
|
||||
curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP)
|
||||
mkdir -p $(FIRST_GOPATH)/bin
|
||||
cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu
|
||||
rm -r $(PROMU_TMP)
|
||||
|
||||
.PHONY: proto
|
||||
proto:
|
||||
@echo ">> generating code from proto files"
|
||||
@./scripts/genproto.sh
|
||||
|
||||
ifdef GOLANGCI_LINT
|
||||
$(GOLANGCI_LINT):
|
||||
mkdir -p $(FIRST_GOPATH)/bin
|
||||
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \
|
||||
| sed -e '/install -d/d' \
|
||||
| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
|
||||
endif
|
||||
|
||||
.PHONY: precheck
|
||||
precheck::
|
||||
|
||||
define PRECHECK_COMMAND_template =
|
||||
precheck:: $(1)_precheck
|
||||
|
||||
PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1)))
|
||||
.PHONY: $(1)_precheck
|
||||
$(1)_precheck:
|
||||
@if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \
|
||||
echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endef
|
561
README.md
561
README.md
|
@ -8,46 +8,117 @@
|
|||
|
||||
## Overview
|
||||
|
||||
### With StatsD
|
||||
The StatsD exporter is a drop-in replacement for StatsD.
|
||||
This exporter translates StatsD metrics to Prometheus metrics via configured mapping rules.
|
||||
|
||||
To pipe metrics from an existing StatsD environment into Prometheus, configure
|
||||
StatsD's repeater backend to repeat all received packets to a `statsd_exporter`
|
||||
process. This exporter translates StatsD metrics to Prometheus metrics via
|
||||
configured mapping rules.
|
||||
We recommend using the exporter only as an intermediate solution, and switching to [native Prometheus instrumentation](http://prometheus.io/docs/instrumenting/clientlibs/) in the long term.
|
||||
While it is common to run centralized StatsD servers, the exporter works best as a [sidecar](https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar).
|
||||
|
||||
+----------+ +-------------------+ +--------------+
|
||||
| StatsD |---(UDP repeater)--->| statsd_exporter |<---(scrape /metrics)---| Prometheus |
|
||||
+----------+ +-------------------+ +--------------+
|
||||
### Transitioning from an existing StatsD setup
|
||||
|
||||
### Without StatsD
|
||||
The relay feature allows for a gradual transition.
|
||||
|
||||
Since the StatsD exporter uses the same UDP protocol as StatsD itself, you can
|
||||
also configure your applications to send StatsD metrics directly to the exporter.
|
||||
In that case, you don't need to run a StatsD server anymore.
|
||||
Introduce the exporter by adding it as a sidecar alongside the application instances.
|
||||
In Kubernetes, this means adding it to the [pod](https://kubernetes.io/docs/concepts/workloads/pods/).
|
||||
Use the `--statsd.relay.address` to forward metrics to your existing StatsD UDP endpoint.
|
||||
Relaying forwards statsd events unmodified, preserving the original metric name and tags in any format.
|
||||
|
||||
We recommend this only as an intermediate solution and recommend switching to
|
||||
[native Prometheus instrumentation](http://prometheus.io/docs/instrumenting/clientlibs/)
|
||||
in the long term.
|
||||
+-------------+ +----------+ +------------+
|
||||
| Application +--->| Exporter +----------------->| StatsD |
|
||||
+-------------+ +----------+ +------------+
|
||||
^
|
||||
| +------------+
|
||||
+----------------------+ Prometheus |
|
||||
+------------+
|
||||
|
||||
### DogStatsD extensions
|
||||
### Relaying from StatsD
|
||||
|
||||
The exporter will convert DogStatsD-style tags to prometheus labels. See
|
||||
[Tags](http://docs.datadoghq.com/guides/dogstatsd/#tags) in the DogStatsD
|
||||
documentation for the concept description and
|
||||
[Datagram Format](http://docs.datadoghq.com/guides/dogstatsd/#datagram-format)
|
||||
for specifics. It boils down to appending
|
||||
`|#tag:value,another_tag:another_value` to the normal StatsD format. Tags
|
||||
without values (`#some_tag`) are not supported.
|
||||
To pipe metrics from an existing StatsD environment into Prometheus, configure StatsD's repeater backend to repeat all received metrics to a `statsd_exporter` process.
|
||||
|
||||
+----------+ +-------------------+ +--------------+
|
||||
| StatsD |---(UDP/TCP repeater)--->| statsd_exporter |<---(scrape /metrics)---| Prometheus |
|
||||
+----------+ +-------------------+ +--------------+
|
||||
|
||||
This allows trying out the exporter with minimal effort, but does not provide the per-instance metrics of the sidecar pattern.
|
||||
|
||||
### Tagging Extensions
|
||||
|
||||
The exporter supports Librato, InfluxDB, DogStatsD, and SignalFX-style tags,
|
||||
which will be converted into Prometheus labels.
|
||||
|
||||
For Librato-style tags, they must be appended to the metric name with a
|
||||
delimiting `#`, as so:
|
||||
|
||||
```
|
||||
metric.name#tagName=val,tag2Name=val2:0|c
|
||||
```
|
||||
|
||||
See the [statsd-librato-backend README](https://github.com/librato/statsd-librato-backend#tags)
|
||||
for a more complete description.
|
||||
|
||||
For InfluxDB-style tags, they must be appended to the metric name with a
|
||||
delimiting comma, as so:
|
||||
|
||||
```
|
||||
metric.name,tagName=val,tag2Name=val2:0|c
|
||||
```
|
||||
|
||||
See [this InfluxDB blog post](https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/#introducing-influx-statsd)
|
||||
for a larger overview.
|
||||
|
||||
|
||||
For DogStatsD-style tags, they're appended as a `|#` delimited section at the
|
||||
end of the metric, as so:
|
||||
|
||||
```
|
||||
metric.name:0|c|#tagName:val,tag2Name:val2
|
||||
```
|
||||
|
||||
See [Tags](https://docs.datadoghq.com/developers/dogstatsd/data_types/#tagging)
|
||||
in the DogStatsD documentation for the concept description and
|
||||
[Datagram Format](https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/).
|
||||
If you encounter problems, note that this tagging style is incompatible with
|
||||
the original `statsd` implementation.
|
||||
The exporter also supports [DogStatD extended aggregations](https://github.com/prometheus/statsd_exporter/pull/558) in combination with DogStatsD tags, but not other tagging styles.
|
||||
|
||||
For [SignalFX dimension](https://github.com/signalfx/signalfx-agent/blob/main/docs/monitors/collectd-statsd.md#adding-dimensions-to-statsd-metrics), add the tags to the metric name in square brackets, as so:
|
||||
|
||||
```
|
||||
metric.name[tagName=val,tag2Name=val2]:0|c
|
||||
```
|
||||
|
||||
Be aware: If you mix tag styles (e.g., Librato/InfluxDB with DogStatsD), the exporter will consider this an error and the behavior is undefined.
|
||||
Also, tags without values (`#some_tag`) are not supported and will be ignored.
|
||||
|
||||
The exporter parses all tagging formats by default, but individual tagging formats can be disabled with command line flags:
|
||||
```
|
||||
--no-statsd.parse-dogstatsd-tags
|
||||
--no-statsd.parse-influxdb-tags
|
||||
--no-statsd.parse-librato-tags
|
||||
--no-statsd.parse-signalfx-tags
|
||||
```
|
||||
|
||||
By default, labels explicitly specified in configuration take precedence over labels from tags.
|
||||
To set the label from the statsd event tag, use [`honor_labels`](#honor-labels).
|
||||
|
||||
## Building and Running
|
||||
|
||||
$ go build
|
||||
$ ./statsd_exporter --help
|
||||
Usage of ./statsd_exporter:
|
||||
-statsd.listen-address=":9125": The UDP address on which to receive statsd metric lines.
|
||||
-statsd.mapping-config="": Metric mapping configuration file name.
|
||||
-web.listen-address=":9102": The address on which to expose the web interface and generated Prometheus metrics.
|
||||
-web.telemetry-path="/metrics": Path under which to expose metrics.
|
||||
NOTE: Version 0.7.0 switched to the [kingpin](https://github.com/alecthomas/kingpin) flags library. With this change, flag behaviour is POSIX-ish:
|
||||
|
||||
* long flags start with two dashes (`--version`)
|
||||
* boolean long flags are disabled by prefixing with no (`--flag-name` is true, `--no-flag-name` is false)
|
||||
* multiple short flags can be combined (but there currently is only one)
|
||||
* flag processing stops at the first `--`
|
||||
* see `--help` for a full list of flags
|
||||
|
||||
## Lifecycle API
|
||||
|
||||
The `statsd_exporter` has an optional lifecycle API (disabled by default) that can be used to reload or quit the exporter
|
||||
by sending a `PUT` or `POST` request to the `/-/reload` or `/-/quit` endpoints.
|
||||
|
||||
## Relay
|
||||
|
||||
The `statsd_exporter` has an optional mode that will buffer and relay incoming statsd lines to a remote server. This is useful to "tee" the data when migrating to using the exporter. The relay will flush the buffer at least once per second to avoid delaying delivery of metrics.
|
||||
|
||||
## Tests
|
||||
|
||||
|
@ -56,8 +127,10 @@ without values (`#some_tag`) are not supported.
|
|||
## Metric Mapping and Configuration
|
||||
|
||||
The `statsd_exporter` can be configured to translate specific dot-separated StatsD
|
||||
metrics into labeled Prometheus metrics via a simple mapping language. A
|
||||
mapping definition starts with a line matching the StatsD metric in question,
|
||||
metrics into labeled Prometheus metrics via a simple mapping language. The config
|
||||
file is reloaded on SIGHUP.
|
||||
|
||||
A mapping definition starts with a line matching the StatsD metric in question,
|
||||
with `*`s acting as wildcards for each dot-separated metric component. The
|
||||
lines following the matching expression must contain one `label="value"` pair
|
||||
each, and at least define the metric name (label name `name`). The Prometheus
|
||||
|
@ -67,8 +140,8 @@ starting at 1. Multiple matching definitions are separated by one or more empty
|
|||
lines. The first mapping rule that matches a StatsD metric wins.
|
||||
|
||||
Metrics that don't match any mapping in the configuration file are translated
|
||||
into Prometheus metrics without any labels and with certain characters escaped
|
||||
(`_` -> `__`; `-` -> `__`; `.` -> `_`).
|
||||
into Prometheus metrics without any labels and with any non-alphanumeric
|
||||
characters, including periods, translated into underscores.
|
||||
|
||||
In general, the different metric types are translated as follows:
|
||||
|
||||
|
@ -76,30 +149,31 @@ In general, the different metric types are translated as follows:
|
|||
|
||||
StatsD counter -> Prometheus counter
|
||||
|
||||
StatsD timer -> Prometheus summary <-- indicates timer quantiles
|
||||
-> Prometheus counter (suffix `_total`) <-- indicates total time spent
|
||||
-> Prometheus counter (suffix `_count`) <-- indicates total number of timer events
|
||||
StatsD timer, histogram, distribution -> Prometheus summary or histogram
|
||||
|
||||
If `-statsd.add-suffix` is set, the exporter appends the metric type (`_gauge`,
|
||||
`_counter`, `_timer`) to the resulting metrics. This is enabled by default for
|
||||
backward compatibility but discouraged to use. Instead, it is better to
|
||||
explicitly define the full metric name in your mapping and run the exporter
|
||||
with `-statsd.add-suffix=false`.
|
||||
### Glob matching
|
||||
|
||||
An example mapping configuration with `-statsd.add-suffix=false`:
|
||||
The default (and fastest) `glob` mapping style uses `*` to denote parts of the statsd metric name that may vary.
|
||||
These varying parts can then be referenced in the construction of the Prometheus metric name and labels.
|
||||
|
||||
test.dispatcher.*.*.*
|
||||
name="dispatcher_events_total"
|
||||
processor="$1"
|
||||
action="$2"
|
||||
outcome="$3"
|
||||
job="test_dispatcher"
|
||||
An example mapping configuration:
|
||||
|
||||
*.signup.*.*
|
||||
name="signup_events_total"
|
||||
provider="$2"
|
||||
outcome="$3"
|
||||
job="${1}_server"
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "test.dispatcher.*.*.*"
|
||||
name: "dispatcher_events_total"
|
||||
labels:
|
||||
processor: "$1"
|
||||
action: "$2"
|
||||
outcome: "$3"
|
||||
job: "test_dispatcher"
|
||||
- match: "*.signup.*.*"
|
||||
name: "signup_events_total"
|
||||
labels:
|
||||
provider: "$2"
|
||||
outcome: "$3"
|
||||
job: "${1}_server"
|
||||
```
|
||||
|
||||
This would transform these example StatsD metrics into Prometheus metrics as
|
||||
follows:
|
||||
|
@ -111,24 +185,391 @@ follows:
|
|||
=> signup_events_total{provider="facebook", outcome="failure", job="foo_product_server"}
|
||||
|
||||
test.web-server.foo.bar
|
||||
=> test_web__server_foo_bar{}
|
||||
=> test_web_server_foo_bar{}
|
||||
|
||||
Each mapping in the configuration file must define a `name` for the metric. The
|
||||
metric's name can contain `$n`-style references to be replaced by the n-th
|
||||
wildcard match in the matching line. That allows for dynamic rewrites, such as:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "test.*.*.counter"
|
||||
name: "${2}_total"
|
||||
labels:
|
||||
provider: "$1"
|
||||
```
|
||||
|
||||
Glob matching offers the best performance for common mappings.
|
||||
|
||||
#### Ordering glob rules
|
||||
|
||||
List more specific matches before wildcards, from left to right:
|
||||
|
||||
a.b.c
|
||||
a.b.*
|
||||
a.*.d
|
||||
a.*.*
|
||||
|
||||
This avoids unexpected shadowing of later rules, and performance impact from backtracking.
|
||||
|
||||
Alternatively, you can disable mapping ordering altogether.
|
||||
With unordered mapping, at each hierarchy level the most specific match wins.
|
||||
This has the same effect as using the recommended ordering.
|
||||
|
||||
### Regular expression matching
|
||||
|
||||
The `regex` mapping style uses regular expressions to match the full statsd metric name.
|
||||
Use it if the glob mapping is not flexible enough to pull structured data from the available statsd metric names.
|
||||
|
||||
Regular expression matching is significantly slower than glob mapping as all mappings must be tested in order.
|
||||
Because of this, **regex mappings are only executed after all glob mappings**.
|
||||
In other words, glob mappings take preference over regex matches, irrespective of the order in which they are specified.
|
||||
Regular expression matches are always evaluated in order, and the first match wins.
|
||||
|
||||
The metric name can also contain references to regex matches. The mapping above
|
||||
could be written as:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "test\\.(\\w+)\\.(\\w+)\\.counter"
|
||||
match_type: regex
|
||||
name: "${2}_total"
|
||||
labels:
|
||||
provider: "$1"
|
||||
- match: "(.*)\\.(.*)--(.*)\\.status\.(.*)\\.count"
|
||||
match_type: regex
|
||||
name: "request_total"
|
||||
labels:
|
||||
hostname: "$1"
|
||||
exec: "$2"
|
||||
protocol: "$3"
|
||||
code: "$4"
|
||||
```
|
||||
|
||||
Be aware about yaml escape rules as a mapping like the following one will not work.
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "test\\.(\w+)\\.(\w+)\\.counter"
|
||||
match_type: regex
|
||||
name: "${2}_total"
|
||||
labels:
|
||||
provider: "$1"
|
||||
```
|
||||
|
||||
#### Special match groups
|
||||
|
||||
When using regex, the match group `0` is the full match and can be used to attach labels to the metric.
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: ".+"
|
||||
match_type: regex
|
||||
name: "$0"
|
||||
labels:
|
||||
statsd_metric_name: "$0"
|
||||
```
|
||||
|
||||
If a metric `my.statsd_counter` is received, the metric name will **still** be mapped to `my_statsd_counter` (Prometheus compatible name).
|
||||
But the metric will also have the label `statsd_metric_name` with the value `my.statsd_counter` (unchanged value).
|
||||
|
||||
Note: If you use the `match` like the example (i.e. `.+`), be aware that it will be a "catch-all" block. So it should come at the very end of the mapping list.
|
||||
|
||||
The same is not achievable with glob matching, for more details check [this issue](https://github.com/prometheus/statsd_exporter/issues/444).
|
||||
|
||||
### Naming, labels, and help
|
||||
|
||||
Please note that metrics with the same name must also have the same set of
|
||||
label names.
|
||||
|
||||
If the default metric help text is insufficient for your needs you may use the YAML
|
||||
configuration to specify a custom help text for each mapping:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "http.request.*"
|
||||
help: "Total number of http requests"
|
||||
name: "http_requests_total"
|
||||
labels:
|
||||
code: "$1"
|
||||
```
|
||||
|
||||
### Honor labels
|
||||
|
||||
By default, labels specified in the mapping configuration take precedence over tags in the statsd event.
|
||||
|
||||
To set the label value to the original tag value, if present, specify `honor_labels: true` in the mapping configuration.
|
||||
In this case, the label specified in the mapping acts as a default.
|
||||
|
||||
### StatsD timers and distributions
|
||||
|
||||
By default, statsd timers and distributions (collectively "observers") are
|
||||
represented as a Prometheus summary with quantiles. You may optionally
|
||||
configure the [quantiles and acceptable
|
||||
error](https://prometheus.io/docs/practices/histograms/#quantiles), as well
|
||||
as adjusting how the summary metric is aggregated:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "test.timing.*.*.*"
|
||||
observer_type: summary
|
||||
name: "my_timer"
|
||||
labels:
|
||||
provider: "$2"
|
||||
outcome: "$3"
|
||||
job: "${1}_server"
|
||||
summary_options:
|
||||
quantiles:
|
||||
- quantile: 0.99
|
||||
error: 0.001
|
||||
- quantile: 0.95
|
||||
error: 0.01
|
||||
- quantile: 0.9
|
||||
error: 0.05
|
||||
- quantile: 0.5
|
||||
error: 0.005
|
||||
max_age: 30s
|
||||
age_buckets: 3
|
||||
buf_cap: 1000
|
||||
```
|
||||
|
||||
The default quantiles are 0.99, 0.9, and 0.5.
|
||||
|
||||
The default summary age is 10 minutes, the default number of buckets
|
||||
is 5 and the default buffer size is 500.
|
||||
See also the [`golang_client` docs](https://godoc.org/github.com/prometheus/client_golang/prometheus#SummaryOpts).
|
||||
The `max_summary_age` corresponds to `SummaryOptions.MaxAge`, `summary_age_buckets` to `SummaryOptions.AgeBuckets` and `stream_buffer_size` to `SummaryOptions.BufCap`.
|
||||
|
||||
In the configuration, one may also set the observer type to "histogram". For example,
|
||||
to set the observer type for a single timer metric:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "test.timing.*.*.*"
|
||||
observer_type: histogram
|
||||
histogram_options:
|
||||
buckets: [ 0.01, 0.025, 0.05, 0.1 ]
|
||||
native_histogram_bucket_factor: 1.1
|
||||
native_histogram_max_buckets: 256
|
||||
name: "my_timer"
|
||||
labels:
|
||||
provider: "$2"
|
||||
outcome: "$3"
|
||||
job: "${1}_server"
|
||||
```
|
||||
|
||||
If not set, then the default
|
||||
[Prometheus client
|
||||
values](https://godoc.org/github.com/prometheus/client_golang/prometheus#pkg-variables)
|
||||
are used for the histogram buckets:
|
||||
`[.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10]`.
|
||||
`+Inf` is added automatically.
|
||||
If your Prometheus server is enabled to scrape native histograms (v2.40.0+),
|
||||
then you can set the `native_histogram_bucket_factor` to configure precision of the
|
||||
buckets in the sparse histogram. More about this in the original [client_golang docs](https://github.com/prometheus/client_golang/blob/449b46435075e6e069e05af920fe028b941033cf/prometheus/histogram.go#L399-L430).
|
||||
Also, a configuration of the maximum number of buckets can be set with `native_histogram_max_buckets`, this
|
||||
avoids the histograms to grow too large in memory. More about this in the original [client_golang docs](https://github.com/prometheus/client_golang/blob/449b46435075e6e069e05af920fe028b941033cf/prometheus/histogram.go#L443-L467).
|
||||
|
||||
`observer_type` is only used when the statsd metric type is a timer, histogram, or distribution.
|
||||
`buckets` is only used when the statsd metric type is one of these, and the `observer_type` is set to `histogram`.
|
||||
|
||||
Timers will be accepted with the `ms` statsd type.
|
||||
Statsd timer data is transmitted in milliseconds, while Prometheus expects the unit to be seconds.
|
||||
The exporter converts all timer observations to seconds.
|
||||
|
||||
Histogram and distribution events (`h` and `d` metric type) are not subject to unit conversion.
|
||||
|
||||
### DogStatsD Client Behavior
|
||||
|
||||
#### `timed()` decorator
|
||||
|
||||
The DogStatsD client's [timed](https://datadogpy.readthedocs.io/en/latest/#datadog.threadstats.base.ThreadStats.timed) decorator emits the metric in seconds but uses the `ms` type.
|
||||
Set [`use_ms=True`](https://datadogpy.readthedocs.io/en/latest/index.html?highlight=use_ms) to send the correct units.
|
||||
|
||||
### Regular expression matching
|
||||
|
||||
Another capability when using YAML configuration is the ability to define matches
|
||||
using raw regular expressions as opposed to the default globbing style of match.
|
||||
This may allow for pulling structured data from otherwise poorly named statsd
|
||||
metrics AND allow for more precise targetting of match rules. When no `match_type`
|
||||
parameter is specified the default value of `glob` will be assumed:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: "(.*)\\.(.*)--(.*)\\.status\\.(.*)\\.count"
|
||||
match_type: regex
|
||||
name: "request_total"
|
||||
labels:
|
||||
hostname: "$1"
|
||||
exec: "$2"
|
||||
protocol: "$3"
|
||||
code: "$4"
|
||||
```
|
||||
|
||||
### Global defaults
|
||||
|
||||
One may also set defaults for the observer type, histogram options, summary options, and match type.
|
||||
These will be used by all mappings that do not define them.
|
||||
|
||||
An option that can only be configured in `defaults` is `glob_disable_ordering`, which is `false` if omitted.
|
||||
By setting this to `true`, `glob` match type will not honor the occurance of rules in the mapping rules file and always treat `*` as lower priority than a concrete string.
|
||||
|
||||
Setting `buckets` or `quantiles` in the defaults is deprecated in favor of `histogram_options` and `summary_options`, which will override the deprecated values.
|
||||
|
||||
If `summary_options` is present in a mapping config, it will only override the fields set in the mapping. Unset fields in the mapping will take the values from the defaults.
|
||||
|
||||
```yaml
|
||||
defaults:
|
||||
observer_type: histogram
|
||||
histogram_options:
|
||||
buckets: [.005, .01, .025, .05, .1, .25, .5, 1, 2.5 ]
|
||||
native_histogram_bucket_factor: 1.1
|
||||
native_histogram_max_buckets: 256
|
||||
summary_options:
|
||||
quantiles:
|
||||
- quantile: 0.99
|
||||
error: 0.001
|
||||
- quantile: 0.95
|
||||
error: 0.01
|
||||
- quantile: 0.9
|
||||
error: 0.05
|
||||
- quantile: 0.5
|
||||
error: 0.005
|
||||
max_age: 5m
|
||||
age_buckets: 2
|
||||
buf_cap: 1000
|
||||
match_type: glob
|
||||
glob_disable_ordering: false
|
||||
ttl: 0 # metrics do not expire
|
||||
mappings:
|
||||
# This will be a histogram using the buckets set in `defaults`.
|
||||
- match: "test.timing.*.*.*"
|
||||
name: "my_timer"
|
||||
labels:
|
||||
provider: "$2"
|
||||
outcome: "$3"
|
||||
job: "${1}_server"
|
||||
# This will be a summary using the summary_options set in `defaults`
|
||||
- match: "other.distribution.*.*.*"
|
||||
observer_type: summary
|
||||
name: "other_distribution"
|
||||
labels:
|
||||
provider: "$2"
|
||||
outcome: "$3"
|
||||
job: "${1}_server_other"
|
||||
```
|
||||
|
||||
### `drop` action
|
||||
|
||||
You may also drop metrics by specifying a "drop" action on a match. For
|
||||
example:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
# This metric would match as normal.
|
||||
- match: "test.timing.*.*.*"
|
||||
name: "my_timer"
|
||||
labels:
|
||||
provider: "$2"
|
||||
outcome: "$3"
|
||||
job: "${1}_server"
|
||||
# Any metric not matched will be dropped because "." matches all metrics.
|
||||
- match: "."
|
||||
match_type: regex
|
||||
action: drop
|
||||
name: "dropped"
|
||||
```
|
||||
|
||||
You can drop any metric using the normal match syntax.
|
||||
The default action is "map" which does the normal metrics mapping.
|
||||
|
||||
### Explicit metric type mapping
|
||||
|
||||
StatsD allows emitting of different metric types under the same metric name,
|
||||
but the Prometheus client library can't merge those. For this use-case the
|
||||
mapping definition allows you to specify which metric type to match:
|
||||
|
||||
```
|
||||
mappings:
|
||||
- match: "test.foo.*"
|
||||
name: "test_foo"
|
||||
match_metric_type: counter
|
||||
labels:
|
||||
provider: "$1"
|
||||
```
|
||||
|
||||
Possible values for `match_metric_type` are `gauge`, `counter` and `observer`.
|
||||
|
||||
### Mapping cache size and cache replacement policy
|
||||
|
||||
There is a cache used to improve the performance of the metric mapping, that can greatly improvement performance.
|
||||
The cache has a default maximum of 1000 unique statsd metric names -> prometheus metrics mappings that it can store.
|
||||
This maximum can be adjusted using the `statsd.cache-size` flag.
|
||||
|
||||
If the maximum is reached, entries are by default rotated using the [least recently used replacement policy](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)). This strategy is optimal when memory is constrained as only the most recent entries are retained.
|
||||
|
||||
Alternatively, you can choose a [random-replacement cache strategy](https://en.wikipedia.org/wiki/Cache_replacement_policies#Random_replacement_(RR)). This is less optimal if the cache is smaller than the cacheable set, but requires less locking. Use this for very high throughput, but make sure to allow for a cache that holds all metrics.
|
||||
|
||||
The optimal cache size is determined by the cardinality of the _incoming_ metrics.
|
||||
|
||||
### Time series expiration
|
||||
|
||||
The `ttl` parameter can be used to define the expiration time for stale metrics.
|
||||
The value is a time duration with valid time units: "ns", "us" (or "µs"),
|
||||
"ms", "s", "m", "h". For example, `ttl: 1m20s`. `0` value is used to indicate
|
||||
metrics that do not expire.
|
||||
|
||||
TTL configuration is stored for each mapped metric name/labels combination
|
||||
whenever new samples are received. This means that you cannot immediately
|
||||
expire a metric only by changing the mapping configuration. At least one
|
||||
sample must be received for updated mappings to take effect.
|
||||
|
||||
### Unit conversions
|
||||
|
||||
The `scale` parameter can be used to define unit conversions for metric values. The value is a floating point number to scale metric values by. This can be useful for converting non-base units (e.g. milliseconds, kilobytes) to base units (e.g. seconds, bytes) as recommended in [prometheus best practices](https://prometheus.io/docs/practices/naming/).
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: foo.latency_ms
|
||||
name: foo_latency_seconds
|
||||
scale: 0.001
|
||||
- match: bar.processed_kb
|
||||
name: bar_processed_bytes
|
||||
scale: 1024
|
||||
- match: baz.latency_us
|
||||
name: baz_latency_seconds
|
||||
scale: 1e-6
|
||||
```
|
||||
|
||||
### Event flushing configuration
|
||||
|
||||
Internally `statsd_exporter` runs a goroutine for each network listener (UDP, TCP & Unix Socket). These each receive and parse metrics received into an event. For performance purposes, these events are queued internally and flushed to the main exporter goroutine periodically in batches. The size of this queue and the flush criteria can be tuned with the `--statsd.event-queue-size`, `--statsd.event-flush-threshold` and `--statsd.event-flush-interval`. However, the defaults should perform well even for very high traffic environments.
|
||||
|
||||
## Using Docker
|
||||
|
||||
You can deploy this exporter using the [prom/statsd-exporter](https://registry.hub.docker.com/u/prom/statsd-exporter/) Docker image.
|
||||
You can deploy this exporter using the [prom/statsd-exporter](https://registry.hub.docker.com/r/prom/statsd-exporter) Docker image.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
docker pull prom/statsd-exporter
|
||||
|
||||
docker run -d -p 9102:9102 -p 9125:9125/udp \
|
||||
-v $PWD/statsd_mapping.conf:/tmp/statsd_mapping.conf \
|
||||
prom/statsd-exporter -statsd.mapping-config=/tmp/statsd_mapping.conf
|
||||
docker run -d -p 9102:9102 -p 9125:9125 -p 9125:9125/udp \
|
||||
-v $PWD/statsd_mapping.yml:/tmp/statsd_mapping.yml \
|
||||
prom/statsd-exporter --statsd.mapping-config=/tmp/statsd_mapping.yml
|
||||
```
|
||||
|
||||
## Library packages
|
||||
|
||||
Parts of the implementation of this exporter are available as separate packages.
|
||||
See the [documentation](https://pkg.go.dev/github.com/prometheus/statsd_exporter/pkg) for details.
|
||||
|
||||
For the time being, there are *no stability guarantees* for library interfaces.
|
||||
We will try to call out any significant changes in the [changelog](https://github.com/prometheus/statsd_exporter/blob/master/CHANGELOG.md).
|
||||
Semantic versioning of the exporter is based on the impact on users of the exporter, not users of the library.
|
||||
|
||||
We encourage re-use of these packages and welcome [issues](https://github.com/prometheus/statsd_exporter/issues?q=is%3Aopen+is%3Aissue+label%3Alibrary) related to their usability as a library.
|
||||
|
||||
[travis]: https://travis-ci.org/prometheus/statsd_exporter
|
||||
[circleci]: https://circleci.com/gh/prometheus/statsd_exporter
|
||||
[quay]: https://quay.io/repository/prometheus/statsd-exporter
|
||||
[hub]: https://hub.docker.com/r/prom/statsd-exporter/
|
||||
|
|
6
SECURITY.md
Normal file
6
SECURITY.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Reporting a security issue
|
||||
|
||||
The Prometheus security policy, including how to report vulnerabilities, can be
|
||||
found here:
|
||||
|
||||
<https://prometheus.io/docs/operating/security/>
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.3.0
|
||||
0.27.1
|
||||
|
|
847
bridge_test.go
847
bridge_test.go
|
@ -14,163 +14,487 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/clock"
|
||||
"github.com/prometheus/statsd_exporter/pkg/event"
|
||||
"github.com/prometheus/statsd_exporter/pkg/exporter"
|
||||
"github.com/prometheus/statsd_exporter/pkg/line"
|
||||
"github.com/prometheus/statsd_exporter/pkg/listener"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper"
|
||||
)
|
||||
|
||||
func TestHandlePacket(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
in string
|
||||
out Events
|
||||
out event.Events
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
}, {
|
||||
name: "simple counter",
|
||||
in: "foo:2|c",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 2,
|
||||
labels: map[string]string{},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 2,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "simple gauge",
|
||||
in: "foo:3|g",
|
||||
out: Events{
|
||||
&GaugeEvent{
|
||||
metricName: "foo",
|
||||
value: 3,
|
||||
labels: map[string]string{},
|
||||
out: event.Events{
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foo",
|
||||
GValue: 3,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "gauge with sampling",
|
||||
in: "foo:3|g|@0.2",
|
||||
out: event.Events{
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foo",
|
||||
GValue: 3,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "gauge decrement",
|
||||
in: "foo:-10|g",
|
||||
out: event.Events{
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foo",
|
||||
GValue: -10,
|
||||
GRelative: true,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "gauge increment",
|
||||
in: "foo:+10|g",
|
||||
out: event.Events{
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foo",
|
||||
GValue: 10,
|
||||
GRelative: true,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "gauge set negative",
|
||||
in: "foo:0|g\nfoo:-1|g",
|
||||
out: event.Events{
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foo",
|
||||
GValue: 0,
|
||||
GRelative: false,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foo",
|
||||
GValue: -1,
|
||||
GRelative: true,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
// Test the sequence given here https://github.com/statsd/statsd/blob/master/docs/metric_types.md#gauges
|
||||
name: "gauge up and down",
|
||||
in: "gaugor:333|g\ngaugor:-10|g\ngaugor:+4|g",
|
||||
out: event.Events{
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "gaugor",
|
||||
GValue: 333,
|
||||
GRelative: false,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "gaugor",
|
||||
GValue: -10,
|
||||
GRelative: true,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "gaugor",
|
||||
GValue: 4,
|
||||
GRelative: true,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "simple timer",
|
||||
in: "foo:200|ms",
|
||||
out: Events{
|
||||
&TimerEvent{
|
||||
metricName: "foo",
|
||||
value: 200,
|
||||
labels: map[string]string{},
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.2,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "simple histogram",
|
||||
in: "foo:200|h",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 200,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "simple distribution",
|
||||
in: "foo:200|d",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 200,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "distribution with sampling",
|
||||
in: "foo:0.01|d|@0.2|#tag1:bar,#tag2:baz",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "librato tag extension",
|
||||
in: "foo#tag1=bar,tag2=baz:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "librato tag extension with tag keys unsupported by prometheus",
|
||||
in: "foo#09digits=0,tag.with.dots=1:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"_09digits": "0", "tag_with_dots": "1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "influxdb tag extension",
|
||||
in: "foo,tag1=bar,tag2=baz:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "SignalFx tag extension",
|
||||
in: "foo.[tag1=bar,tag2=baz]test:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo.test",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "SignalFx tag extension, tags at end of name",
|
||||
in: "foo.test[tag1=bar,tag2=baz]:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo.test",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "SignalFx tag extension, tags at beginning of name",
|
||||
in: "[tag1=bar,tag2=baz]foo.test:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo.test",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "SignalFx tag extension, no tags",
|
||||
in: "foo.[]test:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo.test",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "SignalFx tag extension, non-kv tags",
|
||||
in: "foo.[tag1,tag2]test:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo.test",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "SignalFx tag extension, missing closing bracket",
|
||||
in: "[tag1=bar,tag2=bazfoo.test:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "[tag1=bar,tag2=bazfoo.test",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "SignalFx tag extension, missing opening bracket",
|
||||
in: "tag1=bar,tag2=baz]foo.test:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "tag1=bar,tag2=baz]foo.test",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "influxdb tag extension with tag keys unsupported by prometheus",
|
||||
in: "foo,09digits=0,tag.with.dots=1:100|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"_09digits": "0", "tag_with_dots": "1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension",
|
||||
in: "foo:100|c|#tag1:bar,tag2:baz",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 100,
|
||||
labels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with # in all keys (as sent by datadog php client)",
|
||||
in: "foo:100|c|#tag1:bar,#tag2:baz",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 100,
|
||||
labels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with tag keys unsupported by prometheus",
|
||||
in: "foo:100|c|#09digits:0,tag.with.dots:1",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 100,
|
||||
labels: map[string]string{"_09digits": "0", "tag_with_dots": "1"},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"_09digits": "0", "tag_with_dots": "1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with valueless tags: ignored",
|
||||
in: "foo:100|c|#tag_without_a_value",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 100,
|
||||
labels: map[string]string{},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with valueless tags (edge case)",
|
||||
in: "foo:100|c|#tag_without_a_value,tag:value",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 100,
|
||||
labels: map[string]string{"tag": "value"},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag": "value"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with empty tags (edge case)",
|
||||
in: "foo:100|c|#tag:value,,",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 100,
|
||||
labels: map[string]string{"tag": "value"},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{"tag": "value"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with sampling",
|
||||
in: "foo:100|c|@0.1|#tag1:bar,#tag2:baz",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 1000,
|
||||
labels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 1000,
|
||||
CLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "librato/dogstatsd mixed tag styles without sampling",
|
||||
in: "foo#tag1=foo,tag3=bing:100|c|#tag1:bar,#tag2:baz",
|
||||
out: event.Events{},
|
||||
}, {
|
||||
name: "signalfx/dogstatsd mixed tag styles without sampling",
|
||||
in: "foo[tag1=foo,tag3=bing]:100|c|#tag1:bar,#tag2:baz",
|
||||
out: event.Events{},
|
||||
}, {
|
||||
name: "influxdb/dogstatsd mixed tag styles without sampling",
|
||||
in: "foo,tag1=foo,tag3=bing:100|c|#tag1:bar,#tag2:baz",
|
||||
out: event.Events{},
|
||||
}, {
|
||||
name: "mixed tag styles with sampling",
|
||||
in: "foo#tag1=foo,tag3=bing:100|c|@0.1|#tag1:bar,#tag2:baz",
|
||||
out: event.Events{},
|
||||
}, {
|
||||
name: "histogram with sampling",
|
||||
in: "foo:0.01|h|@0.2|#tag1:bar,#tag2:baz",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.01,
|
||||
OLabels: map[string]string{"tag1": "bar", "tag2": "baz"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with multiple colons",
|
||||
in: "foo:100|c|@0.1|#tag1:foo:bar",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 1000,
|
||||
labels: map[string]string{"tag1": "foo:bar"},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 1000,
|
||||
CLabels: map[string]string{"tag1": "foo:bar"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "datadog tag extension with invalid utf8 tag values",
|
||||
in: "foo:100|c|@0.1|#tag:\xc3\x28invalid",
|
||||
}, {
|
||||
name: "datadog tag extension with both valid and invalid utf8 tag values",
|
||||
in: "foo:100|c|@0.1|#tag1:valid,tag2:\xc3\x28invalid",
|
||||
}, {
|
||||
name: "multiple metrics with invalid datadog utf8 tag values",
|
||||
in: "foo:200|c|#tag:value\nfoo:300|c|#tag:\xc3\x28invalid",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 200,
|
||||
CLabels: map[string]string{"tag": "value"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "combined multiline metrics",
|
||||
in: "foo:200|ms:300|ms:5|c|@0.1:6|g\nbar:1|c:5|ms",
|
||||
out: Events{
|
||||
&TimerEvent{
|
||||
metricName: "foo",
|
||||
value: 200,
|
||||
labels: map[string]string{},
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: .200,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
&TimerEvent{
|
||||
metricName: "foo",
|
||||
value: 300,
|
||||
labels: map[string]string{},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: .300,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 50,
|
||||
labels: map[string]string{},
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 50,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
&GaugeEvent{
|
||||
metricName: "foo",
|
||||
value: 6,
|
||||
labels: map[string]string{},
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foo",
|
||||
GValue: 6,
|
||||
GLabels: map[string]string{},
|
||||
},
|
||||
&CounterEvent{
|
||||
metricName: "bar",
|
||||
value: 1,
|
||||
labels: map[string]string{},
|
||||
&event.CounterEvent{
|
||||
CMetricName: "bar",
|
||||
CValue: 1,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
&TimerEvent{
|
||||
metricName: "bar",
|
||||
value: 5,
|
||||
labels: map[string]string{},
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "bar",
|
||||
OValue: .005,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "timings with sampling factor",
|
||||
in: "foo.timing:0.5|ms|@0.1",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
&event.ObserverEvent{OMetricName: "foo.timing", OValue: 0.0005, OLabels: map[string]string{}},
|
||||
},
|
||||
}, {
|
||||
name: "bad line",
|
||||
in: "foo",
|
||||
|
@ -183,48 +507,359 @@ func TestHandlePacket(t *testing.T) {
|
|||
}, {
|
||||
name: "illegal sampling factor",
|
||||
in: "foo:1|c|@bar",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 1,
|
||||
labels: map[string]string{},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 1,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "zero sampling factor",
|
||||
in: "foo:2|c|@0",
|
||||
out: Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: 2,
|
||||
labels: map[string]string{},
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "foo",
|
||||
CValue: 2,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "illegal stat type",
|
||||
in: "foo:2|t",
|
||||
},
|
||||
{
|
||||
name: "empty metric name",
|
||||
in: ":100|ms",
|
||||
},
|
||||
{
|
||||
name: "empty component",
|
||||
in: "foo:1|c|",
|
||||
},
|
||||
{
|
||||
name: "invalid utf8",
|
||||
in: "invalid\xc3\x28utf8:1|c",
|
||||
},
|
||||
{
|
||||
name: "some invalid utf8",
|
||||
in: "valid_utf8:1|c\ninvalid\xc3\x28utf8:1|c",
|
||||
out: event.Events{
|
||||
&event.CounterEvent{
|
||||
CMetricName: "valid_utf8",
|
||||
CValue: 1,
|
||||
CLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "ms timer with conversion to seconds",
|
||||
in: "foo:200|ms",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 0.2,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "histogram with no unit conversion",
|
||||
in: "foo:200|h",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 200,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "distribution with no unit conversion",
|
||||
in: "foo:200|d",
|
||||
out: event.Events{
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "foo",
|
||||
OValue: 200,
|
||||
OLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
l := StatsDListener{}
|
||||
events := make(chan Events, 32)
|
||||
for i, scenario := range scenarios {
|
||||
l.handlePacket([]byte(scenario.in), events)
|
||||
parser := line.NewParser()
|
||||
parser.EnableDogstatsdParsing()
|
||||
parser.EnableInfluxdbParsing()
|
||||
parser.EnableLibratoParsing()
|
||||
parser.EnableSignalFXParsing()
|
||||
|
||||
// Flatten actual events.
|
||||
actual := Events{}
|
||||
for i := 0; i < len(events); i++ {
|
||||
actual = append(actual, <-events...)
|
||||
}
|
||||
for k, l := range []statsDPacketHandler{&listener.StatsDUDPListener{
|
||||
Conn: nil,
|
||||
EventHandler: nil,
|
||||
Logger: log.NewNopLogger(),
|
||||
LineParser: parser,
|
||||
UDPPackets: udpPackets,
|
||||
UDPPacketDrops: udpPacketDrops,
|
||||
LinesReceived: linesReceived,
|
||||
EventsFlushed: eventsFlushed,
|
||||
SampleErrors: *sampleErrors,
|
||||
SamplesReceived: samplesReceived,
|
||||
TagErrors: tagErrors,
|
||||
TagsReceived: tagsReceived,
|
||||
}, &mockStatsDTCPListener{listener.StatsDTCPListener{
|
||||
Conn: nil,
|
||||
EventHandler: nil,
|
||||
Logger: log.NewNopLogger(),
|
||||
LineParser: parser,
|
||||
LinesReceived: linesReceived,
|
||||
EventsFlushed: eventsFlushed,
|
||||
SampleErrors: *sampleErrors,
|
||||
SamplesReceived: samplesReceived,
|
||||
TagErrors: tagErrors,
|
||||
TagsReceived: tagsReceived,
|
||||
TCPConnections: tcpConnections,
|
||||
TCPErrors: tcpErrors,
|
||||
TCPLineTooLong: tcpLineTooLong,
|
||||
}, log.NewNopLogger()}} {
|
||||
events := make(chan event.Events, 32)
|
||||
l.SetEventHandler(&event.UnbufferedEventHandler{C: events})
|
||||
for i, scenario := range scenarios {
|
||||
l.HandlePacket([]byte(scenario.in))
|
||||
|
||||
if len(actual) != len(scenario.out) {
|
||||
t.Fatalf("%d. Expected %d events, got %d in scenario '%s'", i, len(scenario.out), len(actual), scenario.name)
|
||||
}
|
||||
le := len(events)
|
||||
// Flatten actual events.
|
||||
actual := event.Events{}
|
||||
for j := 0; j < le; j++ {
|
||||
actual = append(actual, <-events...)
|
||||
}
|
||||
|
||||
for j, expected := range scenario.out {
|
||||
if !reflect.DeepEqual(&expected, &actual[j]) {
|
||||
t.Fatalf("%d.%d. Expected %#v, got %#v in scenario '%s'", i, j, expected, actual[j], scenario.name)
|
||||
if len(actual) != len(scenario.out) {
|
||||
t.Fatalf("%d.%d. Expected %d events, got %d in scenario '%s'", k, i, len(scenario.out), len(actual), scenario.name)
|
||||
}
|
||||
|
||||
for j, expected := range scenario.out {
|
||||
if !reflect.DeepEqual(&expected, &actual[j]) {
|
||||
t.Fatalf("%d.%d.%d. Expected %#v, got %#v in scenario '%s'", k, i, j, expected, actual[j], scenario.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type statsDPacketHandler interface {
|
||||
HandlePacket(packet []byte)
|
||||
SetEventHandler(eh event.EventHandler)
|
||||
}
|
||||
|
||||
type mockStatsDTCPListener struct {
|
||||
listener.StatsDTCPListener
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func (ml *mockStatsDTCPListener) HandlePacket(packet []byte) {
|
||||
// Forcing IPv4 because the TravisCI build environment does not have IPv6
|
||||
// addresses.
|
||||
lc, err := net.ListenTCP("tcp4", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("mockStatsDTCPListener: listen failed: %v", err))
|
||||
}
|
||||
|
||||
defer lc.Close()
|
||||
|
||||
go func() {
|
||||
cc, err := net.DialTCP("tcp", nil, lc.Addr().(*net.TCPAddr))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("mockStatsDTCPListener: dial failed: %v", err))
|
||||
}
|
||||
|
||||
defer cc.Close()
|
||||
|
||||
n, err := cc.Write(packet)
|
||||
if err != nil || n != len(packet) {
|
||||
panic(fmt.Sprintf("mockStatsDTCPListener: write failed: %v,%d", err, n))
|
||||
}
|
||||
}()
|
||||
|
||||
sc, err := lc.AcceptTCP()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("mockStatsDTCPListener: accept failed: %v", err))
|
||||
}
|
||||
ml.HandleConn(sc)
|
||||
}
|
||||
|
||||
// TestTtlExpiration validates expiration of time series.
|
||||
// foobar metric without mapping should expire with default ttl of 1s
|
||||
// bazqux metric should expire with ttl of 2s
|
||||
func TestTtlExpiration(t *testing.T) {
|
||||
// Mock a time.NewTicker
|
||||
tickerCh := make(chan time.Time)
|
||||
clock.ClockInstance = &clock.Clock{
|
||||
TickerCh: tickerCh,
|
||||
}
|
||||
|
||||
config := `
|
||||
defaults:
|
||||
ttl: 1s
|
||||
mappings:
|
||||
- match: bazqux.*
|
||||
name: bazqux
|
||||
ttl: 2s
|
||||
`
|
||||
// Create mapper from config and start an Exporter with a synchronous channel
|
||||
testMapper := &mapper.MetricMapper{}
|
||||
err := testMapper.InitFromYAMLString(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Config load error: %s %s", config, err)
|
||||
}
|
||||
events := make(chan event.Events)
|
||||
defer close(events)
|
||||
go func() {
|
||||
ex := exporter.NewExporter(prometheus.DefaultRegisterer, testMapper, log.NewNopLogger(), eventsActions, eventsUnmapped, errorEventStats, eventStats, conflictingEventStats, metricsCount)
|
||||
ex.Listen(events)
|
||||
}()
|
||||
|
||||
ev := event.Events{
|
||||
// event with default ttl = 1s
|
||||
&event.GaugeEvent{
|
||||
GMetricName: "foobar",
|
||||
GValue: 200,
|
||||
},
|
||||
// event with ttl = 2s from a mapping
|
||||
&event.ObserverEvent{
|
||||
OMetricName: "bazqux.main",
|
||||
OValue: 42,
|
||||
},
|
||||
}
|
||||
|
||||
var metrics []*dto.MetricFamily
|
||||
var foobarValue *float64
|
||||
var bazquxValue *float64
|
||||
|
||||
// Step 1. Send events with statsd metrics.
|
||||
// Send empty Events to wait for events are handled.
|
||||
// saveLabelValues will use fake instant as a lastRegisteredAt time.
|
||||
clock.ClockInstance.Instant = time.Unix(0, 0)
|
||||
events <- ev
|
||||
events <- event.Events{}
|
||||
|
||||
// Check values
|
||||
metrics, err = prometheus.DefaultGatherer.Gather()
|
||||
if err != nil {
|
||||
t.Fatal("Gather should not fail")
|
||||
}
|
||||
foobarValue = getFloat64(metrics, "foobar", prometheus.Labels{})
|
||||
bazquxValue = getFloat64(metrics, "bazqux", prometheus.Labels{})
|
||||
if foobarValue == nil || bazquxValue == nil {
|
||||
t.Fatalf("Gauge `foobar` and Summary `bazqux` should be gathered")
|
||||
}
|
||||
if *foobarValue != 200 {
|
||||
t.Fatalf("Gauge `foobar` observation %f is not expected. Should be 200", *foobarValue)
|
||||
}
|
||||
if *bazquxValue != 42 {
|
||||
t.Fatalf("Summary `bazqux` observation %f is not expected. Should be 42", *bazquxValue)
|
||||
}
|
||||
|
||||
// Step 2. Increase Instant to emulate metrics expiration after 1s
|
||||
clock.ClockInstance.Instant = time.Unix(1, 10)
|
||||
clock.ClockInstance.TickerCh <- time.Unix(0, 0)
|
||||
events <- event.Events{}
|
||||
|
||||
// Check values
|
||||
metrics, err = prometheus.DefaultGatherer.Gather()
|
||||
if err != nil {
|
||||
t.Fatal("Gather should not fail")
|
||||
}
|
||||
foobarValue = getFloat64(metrics, "foobar", prometheus.Labels{})
|
||||
bazquxValue = getFloat64(metrics, "bazqux", prometheus.Labels{})
|
||||
if foobarValue != nil {
|
||||
t.Fatalf("Gauge `foobar` should be expired")
|
||||
}
|
||||
if bazquxValue == nil {
|
||||
t.Fatalf("Summary `bazqux` should be gathered")
|
||||
}
|
||||
if *bazquxValue != 42 {
|
||||
t.Fatalf("Summary `bazqux` observation %f is not expected. Should be 42", *bazquxValue)
|
||||
}
|
||||
|
||||
// Step 3. Increase Instant to emulate metrics expiration after 2s
|
||||
clock.ClockInstance.Instant = time.Unix(2, 200)
|
||||
clock.ClockInstance.TickerCh <- time.Unix(0, 0)
|
||||
events <- event.Events{}
|
||||
|
||||
// Check values
|
||||
metrics, err = prometheus.DefaultGatherer.Gather()
|
||||
if err != nil {
|
||||
t.Fatal("Gather should not fail")
|
||||
}
|
||||
foobarValue = getFloat64(metrics, "foobar", prometheus.Labels{})
|
||||
bazquxValue = getFloat64(metrics, "bazqux", prometheus.Labels{})
|
||||
if bazquxValue != nil {
|
||||
t.Fatalf("Summary `bazqux` should be expired")
|
||||
}
|
||||
if foobarValue != nil {
|
||||
t.Fatalf("Gauge `foobar` should not be gathered after expiration")
|
||||
}
|
||||
}
|
||||
|
||||
// getFloat64 search for metric by name in array of MetricFamily and then search a value by labels.
|
||||
// Method returns a value or nil if metric is not found.
|
||||
func getFloat64(metrics []*dto.MetricFamily, name string, labels prometheus.Labels) *float64 {
|
||||
var metricFamily *dto.MetricFamily
|
||||
for _, m := range metrics {
|
||||
if *m.Name == name {
|
||||
metricFamily = m
|
||||
break
|
||||
}
|
||||
}
|
||||
if metricFamily == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var metric *dto.Metric
|
||||
labelStr := fmt.Sprintf("%v", labels)
|
||||
for _, m := range metricFamily.Metric {
|
||||
l := labelPairsAsLabels(m.GetLabel())
|
||||
ls := fmt.Sprintf("%v", l)
|
||||
if labelStr == ls {
|
||||
metric = m
|
||||
break
|
||||
}
|
||||
}
|
||||
if metric == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var value float64
|
||||
if metric.Gauge != nil {
|
||||
value = metric.Gauge.GetValue()
|
||||
return &value
|
||||
}
|
||||
if metric.Counter != nil {
|
||||
value = metric.Counter.GetValue()
|
||||
return &value
|
||||
}
|
||||
if metric.Histogram != nil {
|
||||
value = metric.Histogram.GetSampleSum()
|
||||
return &value
|
||||
}
|
||||
if metric.Summary != nil {
|
||||
value = metric.Summary.GetSampleSum()
|
||||
return &value
|
||||
}
|
||||
if metric.Untyped != nil {
|
||||
value = metric.Untyped.GetValue()
|
||||
return &value
|
||||
}
|
||||
panic(fmt.Errorf("collected a non-gauge/counter/histogram/summary/untyped metric: %s", metric))
|
||||
}
|
||||
|
||||
func labelPairsAsLabels(pairs []*dto.LabelPair) (labels prometheus.Labels) {
|
||||
labels = prometheus.Labels{}
|
||||
for _, pair := range pairs {
|
||||
if pair.Name == nil {
|
||||
continue
|
||||
}
|
||||
value := ""
|
||||
if pair.Value != nil {
|
||||
value = *pair.Value
|
||||
}
|
||||
labels[*pair.Name] = value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
61
circle.yml
61
circle.yml
|
@ -1,61 +0,0 @@
|
|||
machine:
|
||||
environment:
|
||||
DOCKER_IMAGE_NAME: prom/statsd-exporter
|
||||
QUAY_IMAGE_NAME: quay.io/prometheus/statsd-exporter
|
||||
DOCKER_TEST_IMAGE_NAME: quay.io/prometheus/golang-builder:1.6.2-main
|
||||
REPO_PATH: github.com/prometheus/statsd_exporter
|
||||
pre:
|
||||
- sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.1-circleci'
|
||||
- sudo chmod 0755 /usr/bin/docker
|
||||
- sudo curl -L 'https://github.com/aktau/github-release/releases/download/v0.6.2/linux-amd64-github-release.tar.bz2' | tar xvjf - --strip-components 3 -C $HOME/bin
|
||||
services:
|
||||
- docker
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- make promu
|
||||
- docker info
|
||||
override:
|
||||
- promu crossbuild
|
||||
- ln -s .build/linux-amd64/statsd_exporter statsd_exporter
|
||||
- |
|
||||
if [ -n "$CIRCLE_TAG" ]; then
|
||||
make docker DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME DOCKER_IMAGE_TAG=$CIRCLE_TAG
|
||||
make docker DOCKER_IMAGE_NAME=$QUAY_IMAGE_NAME DOCKER_IMAGE_TAG=$CIRCLE_TAG
|
||||
else
|
||||
make docker DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME
|
||||
make docker DOCKER_IMAGE_NAME=$QUAY_IMAGE_NAME
|
||||
fi
|
||||
post:
|
||||
- mkdir $CIRCLE_ARTIFACTS/binaries/ && cp -a .build/* $CIRCLE_ARTIFACTS/binaries/
|
||||
- docker images
|
||||
|
||||
test:
|
||||
override:
|
||||
- docker run --rm -t -v "$(pwd):/app" "${DOCKER_TEST_IMAGE_NAME}" -i "${REPO_PATH}" -T
|
||||
|
||||
deployment:
|
||||
hub_branch:
|
||||
branch: master
|
||||
owner: prometheus
|
||||
commands:
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
|
||||
- docker login -e $QUAY_EMAIL -u $QUAY_LOGIN -p $QUAY_PASSWORD quay.io
|
||||
- docker push $DOCKER_IMAGE_NAME
|
||||
- docker push $QUAY_IMAGE_NAME
|
||||
hub_tag:
|
||||
tag: /^[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||
owner: prometheus
|
||||
commands:
|
||||
- promu crossbuild tarballs
|
||||
- promu release .tarballs
|
||||
- mkdir $CIRCLE_ARTIFACTS/releases/ && cp -a .tarballs/* $CIRCLE_ARTIFACTS/releases/
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
|
||||
- docker login -e $QUAY_EMAIL -u $QUAY_LOGIN -p $QUAY_PASSWORD quay.io
|
||||
- |
|
||||
if [[ "$CIRCLE_TAG" =~ ^[0-9]+(\.[0-9]+){2}$ ]]; then
|
||||
docker tag "$DOCKER_IMAGE_NAME:$CIRCLE_TAG" "$DOCKER_IMAGE_NAME:latest"
|
||||
docker tag "$QUAY_IMAGE_NAME:$CIRCLE_TAG" "$QUAY_IMAGE_NAME:latest"
|
||||
fi
|
||||
- docker push $DOCKER_IMAGE_NAME
|
||||
- docker push $QUAY_IMAGE_NAME
|
423
exporter.go
423
exporter.go
|
@ -1,423 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHelp = "Metric autogenerated by statsd_exporter."
|
||||
regErrF = "A change of configuration created inconsistent metrics for " +
|
||||
"%q. You have to restart the statsd_exporter, and you should " +
|
||||
"consider the effects on your monitoring setup. Error: %s"
|
||||
)
|
||||
|
||||
var (
|
||||
illegalCharsRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
|
||||
hash = fnv.New64a()
|
||||
strBuf bytes.Buffer // Used for hashing.
|
||||
intBuf = make([]byte, 8)
|
||||
)
|
||||
|
||||
// hashNameAndLabels returns a hash value of the provided name string and all
|
||||
// the label names and values in the provided labels map.
|
||||
//
|
||||
// Not safe for concurrent use! (Uses a shared buffer and hasher to save on
|
||||
// allocations.)
|
||||
func hashNameAndLabels(name string, labels prometheus.Labels) uint64 {
|
||||
hash.Reset()
|
||||
strBuf.Reset()
|
||||
strBuf.WriteString(name)
|
||||
hash.Write(strBuf.Bytes())
|
||||
binary.BigEndian.PutUint64(intBuf, model.LabelsToSignature(labels))
|
||||
hash.Write(intBuf)
|
||||
return hash.Sum64()
|
||||
}
|
||||
|
||||
type CounterContainer struct {
|
||||
Elements map[uint64]prometheus.Counter
|
||||
}
|
||||
|
||||
func NewCounterContainer() *CounterContainer {
|
||||
return &CounterContainer{
|
||||
Elements: make(map[uint64]prometheus.Counter),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CounterContainer) Get(metricName string, labels prometheus.Labels) prometheus.Counter {
|
||||
hash := hashNameAndLabels(metricName, labels)
|
||||
counter, ok := c.Elements[hash]
|
||||
if !ok {
|
||||
counter = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: metricName,
|
||||
Help: defaultHelp,
|
||||
ConstLabels: labels,
|
||||
})
|
||||
c.Elements[hash] = counter
|
||||
if err := prometheus.Register(counter); err != nil {
|
||||
log.Fatalf(regErrF, metricName, err)
|
||||
}
|
||||
}
|
||||
return counter
|
||||
}
|
||||
|
||||
type GaugeContainer struct {
|
||||
Elements map[uint64]prometheus.Gauge
|
||||
}
|
||||
|
||||
func NewGaugeContainer() *GaugeContainer {
|
||||
return &GaugeContainer{
|
||||
Elements: make(map[uint64]prometheus.Gauge),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GaugeContainer) Get(metricName string, labels prometheus.Labels) prometheus.Gauge {
|
||||
hash := hashNameAndLabels(metricName, labels)
|
||||
gauge, ok := c.Elements[hash]
|
||||
if !ok {
|
||||
gauge = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: metricName,
|
||||
Help: defaultHelp,
|
||||
ConstLabels: labels,
|
||||
})
|
||||
c.Elements[hash] = gauge
|
||||
if err := prometheus.Register(gauge); err != nil {
|
||||
log.Fatalf(regErrF, metricName, err)
|
||||
}
|
||||
}
|
||||
return gauge
|
||||
}
|
||||
|
||||
type SummaryContainer struct {
|
||||
Elements map[uint64]prometheus.Summary
|
||||
}
|
||||
|
||||
func NewSummaryContainer() *SummaryContainer {
|
||||
return &SummaryContainer{
|
||||
Elements: make(map[uint64]prometheus.Summary),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels) prometheus.Summary {
|
||||
hash := hashNameAndLabels(metricName, labels)
|
||||
summary, ok := c.Elements[hash]
|
||||
if !ok {
|
||||
summary = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Name: metricName,
|
||||
Help: defaultHelp,
|
||||
ConstLabels: labels,
|
||||
})
|
||||
c.Elements[hash] = summary
|
||||
if err := prometheus.Register(summary); err != nil {
|
||||
log.Fatalf(regErrF, metricName, err)
|
||||
}
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
type Event interface {
|
||||
MetricName() string
|
||||
Value() float64
|
||||
Labels() map[string]string
|
||||
}
|
||||
|
||||
type CounterEvent struct {
|
||||
metricName string
|
||||
value float64
|
||||
labels map[string]string
|
||||
}
|
||||
|
||||
func (c *CounterEvent) MetricName() string { return c.metricName }
|
||||
func (c *CounterEvent) Value() float64 { return c.value }
|
||||
func (c *CounterEvent) Labels() map[string]string { return c.labels }
|
||||
|
||||
type GaugeEvent struct {
|
||||
metricName string
|
||||
value float64
|
||||
labels map[string]string
|
||||
}
|
||||
|
||||
func (g *GaugeEvent) MetricName() string { return g.metricName }
|
||||
func (g *GaugeEvent) Value() float64 { return g.value }
|
||||
func (c *GaugeEvent) Labels() map[string]string { return c.labels }
|
||||
|
||||
type TimerEvent struct {
|
||||
metricName string
|
||||
value float64
|
||||
labels map[string]string
|
||||
}
|
||||
|
||||
func (t *TimerEvent) MetricName() string { return t.metricName }
|
||||
func (t *TimerEvent) Value() float64 { return t.value }
|
||||
func (c *TimerEvent) Labels() map[string]string { return c.labels }
|
||||
|
||||
type Events []Event
|
||||
|
||||
type Exporter struct {
|
||||
Counters *CounterContainer
|
||||
Gauges *GaugeContainer
|
||||
Summaries *SummaryContainer
|
||||
mapper *metricMapper
|
||||
addSuffix bool
|
||||
}
|
||||
|
||||
func escapeMetricName(metricName string) string {
|
||||
// If a metric starts with a digit, prepend an underscore.
|
||||
if metricName[0] >= '0' && metricName[0] <= '9' {
|
||||
metricName = "_" + metricName
|
||||
}
|
||||
|
||||
// Replace all illegal metric chars with underscores.
|
||||
metricName = illegalCharsRE.ReplaceAllString(metricName, "_")
|
||||
return metricName
|
||||
}
|
||||
|
||||
func (b *Exporter) suffix(metricName, suffix string) string {
|
||||
str := metricName
|
||||
if b.addSuffix {
|
||||
str += "_" + suffix
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (b *Exporter) Listen(e <-chan Events) {
|
||||
for {
|
||||
events, ok := <-e
|
||||
if !ok {
|
||||
log.Debug("Channel is closed. Break out of Exporter.Listener.")
|
||||
return
|
||||
}
|
||||
for _, event := range events {
|
||||
metricName := ""
|
||||
prometheusLabels := event.Labels()
|
||||
|
||||
labels, present := b.mapper.getMapping(event.MetricName())
|
||||
if present {
|
||||
metricName = labels["name"]
|
||||
for label, value := range labels {
|
||||
if label != "name" {
|
||||
prometheusLabels[label] = value
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eventsUnmapped.Inc()
|
||||
metricName = escapeMetricName(event.MetricName())
|
||||
}
|
||||
|
||||
switch event.(type) {
|
||||
case *CounterEvent:
|
||||
counter := b.Counters.Get(
|
||||
b.suffix(metricName, "counter"),
|
||||
prometheusLabels,
|
||||
)
|
||||
// We don't accept negative values for counters. Incrementing the counter with a negative number
|
||||
// will cause the exporter to panic. Instead we will warn and continue to the next event.
|
||||
if event.Value() < 0.0 {
|
||||
log.Errorf("Counter %q is: '%f' (counter must be non-negative value)", metricName, event.Value())
|
||||
continue
|
||||
}
|
||||
|
||||
counter.Add(event.Value())
|
||||
|
||||
eventStats.WithLabelValues("counter").Inc()
|
||||
|
||||
case *GaugeEvent:
|
||||
gauge := b.Gauges.Get(
|
||||
b.suffix(metricName, "gauge"),
|
||||
prometheusLabels,
|
||||
)
|
||||
gauge.Set(event.Value())
|
||||
|
||||
eventStats.WithLabelValues("gauge").Inc()
|
||||
|
||||
case *TimerEvent:
|
||||
summary := b.Summaries.Get(
|
||||
b.suffix(metricName, "timer"),
|
||||
prometheusLabels,
|
||||
)
|
||||
summary.Observe(event.Value())
|
||||
|
||||
eventStats.WithLabelValues("timer").Inc()
|
||||
|
||||
default:
|
||||
log.Errorln("Unsupported event type")
|
||||
eventStats.WithLabelValues("illegal").Inc()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewExporter(mapper *metricMapper, addSuffix bool) *Exporter {
|
||||
return &Exporter{
|
||||
addSuffix: addSuffix,
|
||||
Counters: NewCounterContainer(),
|
||||
Gauges: NewGaugeContainer(),
|
||||
Summaries: NewSummaryContainer(),
|
||||
mapper: mapper,
|
||||
}
|
||||
}
|
||||
|
||||
type StatsDListener struct {
|
||||
conn *net.UDPConn
|
||||
}
|
||||
|
||||
func buildEvent(statType, metric string, value float64, labels map[string]string) (Event, error) {
|
||||
switch statType {
|
||||
case "c":
|
||||
return &CounterEvent{
|
||||
metricName: metric,
|
||||
value: float64(value),
|
||||
labels: labels,
|
||||
}, nil
|
||||
case "g":
|
||||
return &GaugeEvent{
|
||||
metricName: metric,
|
||||
value: float64(value),
|
||||
labels: labels,
|
||||
}, nil
|
||||
case "ms", "h":
|
||||
return &TimerEvent{
|
||||
metricName: metric,
|
||||
value: float64(value),
|
||||
labels: labels,
|
||||
}, nil
|
||||
case "s":
|
||||
return nil, fmt.Errorf("No support for StatsD sets")
|
||||
default:
|
||||
return nil, fmt.Errorf("Bad stat type %s", statType)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StatsDListener) Listen(e chan<- Events) {
|
||||
buf := make([]byte, 65535)
|
||||
for {
|
||||
n, _, err := l.conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
l.handlePacket(buf[0:n], e)
|
||||
}
|
||||
}
|
||||
|
||||
func parseDogStatsDTagsToLabels(component string) map[string]string {
|
||||
labels := map[string]string{}
|
||||
networkStats.WithLabelValues("dogstatsd_tags").Inc()
|
||||
tags := strings.Split(component, ",")
|
||||
for _, t := range tags {
|
||||
t = strings.TrimPrefix(t, "#")
|
||||
kv := strings.SplitN(t, ":", 2)
|
||||
|
||||
if len(kv) < 2 || len(kv[1]) == 0 {
|
||||
networkStats.WithLabelValues("malformed_dogstatsd_tag").Inc()
|
||||
log.Errorf("Malformed or empty DogStatsD tag %s in component %s", t, component)
|
||||
continue
|
||||
}
|
||||
|
||||
labels[escapeMetricName(kv[0])] = kv[1]
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) {
|
||||
lines := strings.Split(string(packet), "\n")
|
||||
events := Events{}
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
elements := strings.SplitN(line, ":", 2)
|
||||
if len(elements) < 2 {
|
||||
networkStats.WithLabelValues("malformed_line").Inc()
|
||||
log.Errorln("Bad line from StatsD:", line)
|
||||
continue
|
||||
}
|
||||
metric := elements[0]
|
||||
var samples []string
|
||||
if strings.Contains(elements[1], "|#") {
|
||||
// using datadog extensions, disable multi-metrics
|
||||
samples = elements[1:]
|
||||
} else {
|
||||
samples = strings.Split(elements[1], ":")
|
||||
}
|
||||
for _, sample := range samples {
|
||||
components := strings.Split(sample, "|")
|
||||
samplingFactor := 1.0
|
||||
if len(components) < 2 || len(components) > 4 {
|
||||
networkStats.WithLabelValues("malformed_component").Inc()
|
||||
log.Errorln("Bad component on line:", line)
|
||||
continue
|
||||
}
|
||||
valueStr, statType := components[0], components[1]
|
||||
value, err := strconv.ParseFloat(valueStr, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Bad value %s on line: %s", valueStr, line)
|
||||
networkStats.WithLabelValues("malformed_value").Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
labels := map[string]string{}
|
||||
if len(components) >= 3 {
|
||||
for _, component := range components[2:] {
|
||||
switch component[0] {
|
||||
case '@':
|
||||
if statType != "c" {
|
||||
log.Errorln("Illegal sampling factor for non-counter metric on line", line)
|
||||
networkStats.WithLabelValues("illegal_sample_factor").Inc()
|
||||
}
|
||||
samplingFactor, err = strconv.ParseFloat(component[1:], 64)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid sampling factor %s on line %s", component[1:], line)
|
||||
networkStats.WithLabelValues("invalid_sample_factor").Inc()
|
||||
}
|
||||
if samplingFactor == 0 {
|
||||
samplingFactor = 1
|
||||
}
|
||||
value /= samplingFactor
|
||||
case '#':
|
||||
labels = parseDogStatsDTagsToLabels(component)
|
||||
default:
|
||||
log.Errorf("Invalid sampling factor or tag section %s on line %s", components[2], line)
|
||||
networkStats.WithLabelValues("invalid_sample_factor").Inc()
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event, err := buildEvent(statType, metric, value, labels)
|
||||
if err != nil {
|
||||
log.Errorf("Error building event on line %s: %s", line, err)
|
||||
networkStats.WithLabelValues("illegal_event").Inc()
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
networkStats.WithLabelValues("legal").Inc()
|
||||
}
|
||||
}
|
||||
e <- events
|
||||
}
|
201
exporter_benchmark_test.go
Normal file
201
exporter_benchmark_test.go
Normal file
|
@ -0,0 +1,201 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/event"
|
||||
"github.com/prometheus/statsd_exporter/pkg/exporter"
|
||||
"github.com/prometheus/statsd_exporter/pkg/line"
|
||||
"github.com/prometheus/statsd_exporter/pkg/listener"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper"
|
||||
)
|
||||
|
||||
func benchmarkUDPListener(times int, b *testing.B) {
|
||||
input := []string{
|
||||
"foo1:2|c",
|
||||
"foo2:3|g",
|
||||
"foo3:200|ms",
|
||||
"foo4:100|c|#tag1:bar,tag2:baz",
|
||||
"foo5:100|c|#tag1:bar,#tag2:baz",
|
||||
"foo6:100|c|#09digits:0,tag.with.dots:1",
|
||||
"foo10:100|c|@0.1|#tag1:bar,#tag2:baz",
|
||||
"foo11:100|c|@0.1|#tag1:foo:bar",
|
||||
"foo.[foo=bar,dim=val]test:1|g",
|
||||
"foo15:200|ms:300|ms:5|c|@0.1:6|g\nfoo15a:1|c:5|ms",
|
||||
"some_very_useful_metrics_with_quite_a_log_name:13|c",
|
||||
}
|
||||
bytesInput := make([]string, len(input)*times)
|
||||
logger := log.NewNopLogger()
|
||||
for run := 0; run < times; run++ {
|
||||
for i := 0; i < len(input); i++ {
|
||||
bytesInput[run*len(input)+i] = fmt.Sprintf("run%d%s", run, input[i])
|
||||
}
|
||||
}
|
||||
|
||||
parser := line.NewParser()
|
||||
parser.EnableDogstatsdParsing()
|
||||
parser.EnableInfluxdbParsing()
|
||||
parser.EnableLibratoParsing()
|
||||
parser.EnableSignalFXParsing()
|
||||
|
||||
// reset benchmark timer to not measure startup costs
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
// pause benchmark timer for creating the chan and listener
|
||||
b.StopTimer()
|
||||
|
||||
// there are more events than input lines, need bigger buffer
|
||||
events := make(chan event.Events, len(bytesInput)*times*2)
|
||||
udpChan := make(chan []byte, len(bytesInput)*times*2)
|
||||
|
||||
l := listener.StatsDUDPListener{
|
||||
EventHandler: &event.UnbufferedEventHandler{C: events},
|
||||
Logger: logger,
|
||||
LineParser: parser,
|
||||
UDPPackets: udpPackets,
|
||||
LinesReceived: linesReceived,
|
||||
SamplesReceived: samplesReceived,
|
||||
TagsReceived: tagsReceived,
|
||||
UdpPacketQueue: udpChan,
|
||||
}
|
||||
|
||||
// resume benchmark timer
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < times; i++ {
|
||||
for _, line := range bytesInput {
|
||||
l.HandlePacket([]byte(line))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUDPListener1(b *testing.B) {
|
||||
benchmarkUDPListener(1, b)
|
||||
}
|
||||
func BenchmarkUDPListener5(b *testing.B) {
|
||||
benchmarkUDPListener(5, b)
|
||||
}
|
||||
func BenchmarkUDPListener50(b *testing.B) {
|
||||
benchmarkUDPListener(50, b)
|
||||
}
|
||||
|
||||
func BenchmarkExporterListener(b *testing.B) {
|
||||
events := event.Events{
|
||||
&event.CounterEvent{ // simple counter
|
||||
CMetricName: "counter",
|
||||
CValue: 2,
|
||||
},
|
||||
&event.GaugeEvent{ // simple gauge
|
||||
GMetricName: "gauge",
|
||||
GValue: 10,
|
||||
},
|
||||
&event.ObserverEvent{ // simple timer
|
||||
OMetricName: "timer",
|
||||
OValue: 200,
|
||||
},
|
||||
&event.ObserverEvent{ // simple histogram
|
||||
OMetricName: "histogram.test",
|
||||
OValue: 200,
|
||||
},
|
||||
&event.CounterEvent{ // simple_tags
|
||||
CMetricName: "simple_tags",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{
|
||||
"alpha": "bar",
|
||||
"bravo": "baz",
|
||||
},
|
||||
},
|
||||
&event.CounterEvent{ // slightly different tags
|
||||
CMetricName: "simple_tags",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{
|
||||
"alpha": "bar",
|
||||
"charlie": "baz",
|
||||
},
|
||||
},
|
||||
&event.CounterEvent{ // and even more different tags
|
||||
CMetricName: "simple_tags",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{
|
||||
"alpha": "bar",
|
||||
"bravo": "baz",
|
||||
"golf": "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
|
||||
},
|
||||
},
|
||||
&event.CounterEvent{ // datadog tag extension with complex tags
|
||||
CMetricName: "foo",
|
||||
CValue: 100,
|
||||
CLabels: map[string]string{
|
||||
"action": "test",
|
||||
"application": "testapp",
|
||||
"application_component": "testcomp",
|
||||
"application_role": "test_role",
|
||||
"category": "category",
|
||||
"controller": "controller",
|
||||
"deployed_to": "production",
|
||||
"kube_deployment": "deploy",
|
||||
"kube_namespace": "kube-production",
|
||||
"method": "get",
|
||||
"version": "5.2.8374",
|
||||
"status": "200",
|
||||
"status_range": "2xx",
|
||||
},
|
||||
},
|
||||
}
|
||||
config := `
|
||||
mappings:
|
||||
- match: histogram.test
|
||||
timer_type: histogram
|
||||
name: "histogram_test"
|
||||
`
|
||||
|
||||
testMapper := &mapper.MetricMapper{}
|
||||
err := testMapper.InitFromYAMLString(config)
|
||||
if err != nil {
|
||||
b.Fatalf("Config load error: %s %s", config, err)
|
||||
}
|
||||
|
||||
ex := exporter.NewExporter(prometheus.DefaultRegisterer, testMapper, log.NewNopLogger(), eventsActions, eventsUnmapped, errorEventStats, eventStats, conflictingEventStats, metricsCount)
|
||||
|
||||
// reset benchmark timer to not measure startup costs
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
// pause benchmark timer for creating the chan
|
||||
b.StopTimer()
|
||||
|
||||
ec := make(chan event.Events, 1000)
|
||||
|
||||
// resume benchmark timer
|
||||
b.StartTimer()
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
ec <- events
|
||||
}
|
||||
close(ec)
|
||||
}()
|
||||
|
||||
ex.Listen(ec)
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestNegativeCounter validates when we send a negative
|
||||
// number to a counter that we no longer panic the Exporter Listener.
|
||||
func TestNegativeCounter(t *testing.T) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err := e.(error)
|
||||
if err.Error() == "counter cannot decrease in value" {
|
||||
t.Fatalf("Counter was negative and causes a panic.")
|
||||
} else {
|
||||
t.Fatalf("Unknown panic and error: %q", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
events := make(chan Events, 1)
|
||||
c := Events{
|
||||
&CounterEvent{
|
||||
metricName: "foo",
|
||||
value: -1,
|
||||
},
|
||||
}
|
||||
events <- c
|
||||
ex := NewExporter(&metricMapper{}, true)
|
||||
|
||||
// Close channel to signify we are done with the listener after a short period.
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
close(events)
|
||||
}()
|
||||
|
||||
ex.Listen(events)
|
||||
}
|
36
go.mod
Normal file
36
go.mod
Normal file
|
@ -0,0 +1,36 @@
|
|||
module github.com/prometheus/statsd_exporter
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/prometheus/client_golang v1.20.3
|
||||
github.com/prometheus/client_model v0.6.1
|
||||
github.com/prometheus/common v0.57.0
|
||||
github.com/prometheus/exporter-toolkit v0.11.0
|
||||
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
)
|
85
go.sum
Normal file
85
go.sum
Normal file
|
@ -0,0 +1,85 @@
|
|||
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg=
|
||||
github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY=
|
||||
github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI=
|
||||
github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g=
|
||||
github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807 h1:LUsDduamlucuNnWcaTbXQ6aLILFcLXADpOzeEH3U+OI=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
105
line_benchmark_test.go
Normal file
105
line_benchmark_test.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2020 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/line"
|
||||
)
|
||||
|
||||
var (
|
||||
// just a grab bag of mixed formats, valid, invalid
|
||||
mixedLines = []string{
|
||||
"foo1:2|c",
|
||||
"foo2:3|g",
|
||||
"foo3:200|ms",
|
||||
"foo4:100|c|#tag1:bar,tag2:baz",
|
||||
"foo5:100|c|#tag1:bar,#tag2:baz",
|
||||
"foo6:100|c|#09digits:0,tag.with.dots:1",
|
||||
"foo10:100|c|@0.1|#tag1:bar,#tag2:baz",
|
||||
"foo11:100|c|@0.1|#tag1:foo:bar",
|
||||
"foo.[foo=bar,dim=val]test:1|g",
|
||||
"foo15:200|ms:300|ms:5|c|@0.1:6|g\nfoo15a:1|c:5|ms",
|
||||
"some_very_useful_metrics_with_quite_a_log_name:13|c",
|
||||
}
|
||||
nopLogger = log.NewNopLogger()
|
||||
)
|
||||
|
||||
func benchmarkLinesToEvents(times int, b *testing.B, input []string) {
|
||||
// always report allocations since this is a hot path
|
||||
b.ReportAllocs()
|
||||
|
||||
parser := line.NewParser()
|
||||
parser.EnableDogstatsdParsing()
|
||||
parser.EnableInfluxdbParsing()
|
||||
parser.EnableLibratoParsing()
|
||||
parser.EnableSignalFXParsing()
|
||||
|
||||
// reset benchmark timer to not measure startup costs
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
for i := 0; i < times; i++ {
|
||||
for _, l := range input {
|
||||
parser.LineToEvents(l, *sampleErrors, samplesReceived, tagErrors, tagsReceived, nopLogger)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mixed statsd formats
|
||||
func BenchmarkLineToEventsMixed1(b *testing.B) {
|
||||
benchmarkLinesToEvents(1, b, mixedLines)
|
||||
}
|
||||
func BenchmarkLineToEventsMixed5(b *testing.B) {
|
||||
benchmarkLinesToEvents(5, b, mixedLines)
|
||||
}
|
||||
func BenchmarkLineToEventsMixed50(b *testing.B) {
|
||||
benchmarkLinesToEvents(50, b, mixedLines)
|
||||
}
|
||||
|
||||
func BenchmarkLineFormats(b *testing.B) {
|
||||
input := map[string]string{
|
||||
"statsd": "foo1:2|c",
|
||||
"invalidStatsd": "foo1:2|c||",
|
||||
"dogStatsd": "foo1:100|c|#tag1:bar,tag2:baz",
|
||||
"invalidDogStatsd": "foo3:100|c|#09digits:0,tag.with.dots:1",
|
||||
"signalFx": "foo1.[foo=bar1,dim=val1]test:1|g",
|
||||
"invalidSignalFx": "foo1.[foo=bar1,dim=val1test:1|g",
|
||||
"influxDb": "foo1,tag1=bar,tag2=baz:100|c",
|
||||
"invalidInfluxDb": "foo3,tag1=bar,tag2:100|c",
|
||||
}
|
||||
|
||||
parser := line.NewParser()
|
||||
parser.EnableDogstatsdParsing()
|
||||
parser.EnableInfluxdbParsing()
|
||||
parser.EnableLibratoParsing()
|
||||
parser.EnableSignalFXParsing()
|
||||
|
||||
// reset benchmark timer to not measure startup costs
|
||||
b.ResetTimer()
|
||||
|
||||
for name, l := range input {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
// always report allocations since this is a hot path
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
parser.LineToEvents(l, *sampleErrors, samplesReceived, tagErrors, tagsReceived, nopLogger)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
609
main.go
609
main.go
|
@ -14,151 +14,556 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/howeyc/fsnotify"
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/common/promlog"
|
||||
"github.com/prometheus/common/promlog/flag"
|
||||
"github.com/prometheus/common/version"
|
||||
)
|
||||
"github.com/prometheus/exporter-toolkit/web"
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(version.NewCollector("statsd_exporter"))
|
||||
}
|
||||
"github.com/prometheus/statsd_exporter/pkg/address"
|
||||
"github.com/prometheus/statsd_exporter/pkg/event"
|
||||
"github.com/prometheus/statsd_exporter/pkg/exporter"
|
||||
"github.com/prometheus/statsd_exporter/pkg/level"
|
||||
"github.com/prometheus/statsd_exporter/pkg/line"
|
||||
"github.com/prometheus/statsd_exporter/pkg/listener"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mappercache/lru"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mappercache/randomreplacement"
|
||||
"github.com/prometheus/statsd_exporter/pkg/relay"
|
||||
)
|
||||
|
||||
var (
|
||||
listenAddress = flag.String("web.listen-address", ":9102", "The address on which to expose the web interface and generated Prometheus metrics.")
|
||||
metricsEndpoint = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
|
||||
statsdListenAddress = flag.String("statsd.listen-address", ":9125", "The UDP address on which to receive statsd metric lines.")
|
||||
mappingConfig = flag.String("statsd.mapping-config", "", "Metric mapping configuration file name.")
|
||||
readBuffer = flag.Int("statsd.read-buffer", 0, "Size (in bytes) of the operating system's transmit read buffer associated with the UDP connection. Please make sure the kernel parameters net.core.rmem_max is set to a value greater than the value specified.")
|
||||
addSuffix = flag.Bool("statsd.add-suffix", true, "Add the metric type (counter/gauge/timer) as suffix to the generated Prometheus metric (NOT recommended, but set by default for backward compatibility).")
|
||||
showVersion = flag.Bool("version", false, "Print version information.")
|
||||
eventStats = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_events_total",
|
||||
Help: "The total number of StatsD events seen.",
|
||||
},
|
||||
[]string{"type"},
|
||||
)
|
||||
eventsFlushed = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_event_queue_flushed_total",
|
||||
Help: "Number of times events were flushed to exporter",
|
||||
},
|
||||
)
|
||||
eventsUnmapped = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_events_unmapped_total",
|
||||
Help: "The total number of StatsD events no mapping was found for.",
|
||||
})
|
||||
udpPackets = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_udp_packets_total",
|
||||
Help: "The total number of StatsD packets received over UDP.",
|
||||
},
|
||||
)
|
||||
udpPacketDrops = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_udp_packet_drops_total",
|
||||
Help: "The total number of dropped StatsD packets which received over UDP.",
|
||||
},
|
||||
)
|
||||
tcpConnections = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_tcp_connections_total",
|
||||
Help: "The total number of TCP connections handled.",
|
||||
},
|
||||
)
|
||||
tcpErrors = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_tcp_connection_errors_total",
|
||||
Help: "The number of errors encountered reading from TCP.",
|
||||
},
|
||||
)
|
||||
tcpLineTooLong = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_tcp_too_long_lines_total",
|
||||
Help: "The number of lines discarded due to being too long.",
|
||||
},
|
||||
)
|
||||
unixgramPackets = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_unixgram_packets_total",
|
||||
Help: "The total number of StatsD packets received over Unixgram.",
|
||||
},
|
||||
)
|
||||
linesReceived = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_lines_total",
|
||||
Help: "The total number of StatsD lines received.",
|
||||
},
|
||||
)
|
||||
samplesReceived = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_samples_total",
|
||||
Help: "The total number of StatsD samples received.",
|
||||
},
|
||||
)
|
||||
sampleErrors = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_sample_errors_total",
|
||||
Help: "The total number of errors parsing StatsD samples.",
|
||||
},
|
||||
[]string{"reason"},
|
||||
)
|
||||
tagsReceived = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_tags_total",
|
||||
Help: "The total number of DogStatsD tags processed.",
|
||||
},
|
||||
)
|
||||
tagErrors = promauto.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_tag_errors_total",
|
||||
Help: "The number of errors parsing DogStatsD tags.",
|
||||
},
|
||||
)
|
||||
configLoads = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_config_reloads_total",
|
||||
Help: "The number of configuration reloads.",
|
||||
},
|
||||
[]string{"outcome"},
|
||||
)
|
||||
mappingsCount = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "statsd_exporter_loaded_mappings",
|
||||
Help: "The current number of configured metric mappings.",
|
||||
})
|
||||
conflictingEventStats = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_events_conflict_total",
|
||||
Help: "The total number of StatsD events with conflicting names.",
|
||||
},
|
||||
[]string{"type"},
|
||||
)
|
||||
errorEventStats = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_events_error_total",
|
||||
Help: "The total number of StatsD events discarded due to errors.",
|
||||
},
|
||||
[]string{"reason"},
|
||||
)
|
||||
eventsActions = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_events_actions_total",
|
||||
Help: "The total number of StatsD events by action.",
|
||||
},
|
||||
[]string{"action"},
|
||||
)
|
||||
metricsCount = promauto.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "statsd_exporter_metrics_total",
|
||||
Help: "The total number of metrics.",
|
||||
},
|
||||
[]string{"type"},
|
||||
)
|
||||
)
|
||||
|
||||
func serveHTTP() {
|
||||
http.Handle(*metricsEndpoint, prometheus.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`<html>
|
||||
<head><title>StatsD Bridge</title></head>
|
||||
<body>
|
||||
<h1>StatsD Bridge</h1>
|
||||
<p><a href="` + *metricsEndpoint + `">Metrics</a></p>
|
||||
</body>
|
||||
</html>`))
|
||||
})
|
||||
http.ListenAndServe(*listenAddress, nil)
|
||||
func serveHTTP(mux http.Handler, listenAddress string, logger log.Logger) {
|
||||
level.Error(logger).Log("msg", http.ListenAndServe(listenAddress, mux))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func udpAddrFromString(addr string) *net.UDPAddr {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
log.Fatal("Bad StatsD listening address", addr)
|
||||
}
|
||||
func sighupConfigReloader(fileName string, mapper *mapper.MetricMapper, logger log.Logger) {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGHUP)
|
||||
|
||||
if host == "" {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
ip, err := net.ResolveIPAddr("ip", host)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to resolve %s: %s", host, err)
|
||||
}
|
||||
for s := range signals {
|
||||
if fileName == "" {
|
||||
level.Warn(logger).Log("msg", "Received signal but no mapping config to reload", "signal", s)
|
||||
continue
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil || port < 0 || port > 65535 {
|
||||
log.Fatalf("Bad port %s: %s", portStr, err)
|
||||
}
|
||||
level.Info(logger).Log("msg", "Received signal, attempting reload", "signal", s)
|
||||
|
||||
return &net.UDPAddr{
|
||||
IP: ip.IP,
|
||||
Port: port,
|
||||
Zone: ip.Zone,
|
||||
reloadConfig(fileName, mapper, logger)
|
||||
}
|
||||
}
|
||||
|
||||
func watchConfig(fileName string, mapper *metricMapper) {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
func reloadConfig(fileName string, mapper *mapper.MetricMapper, logger log.Logger) {
|
||||
err := mapper.InitFromFile(fileName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
level.Info(logger).Log("msg", "Error reloading config", "error", err)
|
||||
configLoads.WithLabelValues("failure").Inc()
|
||||
} else {
|
||||
level.Info(logger).Log("msg", "Config reloaded successfully")
|
||||
configLoads.WithLabelValues("success").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
err = watcher.WatchFlags(fileName, fsnotify.FSN_MODIFY)
|
||||
func dumpFSM(mapper *mapper.MetricMapper, dumpFilename string, logger log.Logger) error {
|
||||
f, err := os.Create(dumpFilename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
level.Info(logger).Log("msg", "Start dumping FSM", "file_name", dumpFilename)
|
||||
w := bufio.NewWriter(f)
|
||||
mapper.FSM.DumpFSM(w)
|
||||
w.Flush()
|
||||
f.Close()
|
||||
level.Info(logger).Log("msg", "Finish dumping FSM")
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev := <-watcher.Event:
|
||||
log.Infof("Config file changed (%s), attempting reload", ev)
|
||||
err = mapper.initFromFile(fileName)
|
||||
if err != nil {
|
||||
log.Errorln("Error reloading config:", err)
|
||||
configLoads.WithLabelValues("failure").Inc()
|
||||
} else {
|
||||
log.Infoln("Config reloaded successfully")
|
||||
configLoads.WithLabelValues("success").Inc()
|
||||
}
|
||||
// Re-add the file watcher since it can get lost on some changes. E.g.
|
||||
// saving a file with vim results in a RENAME-MODIFY-DELETE event
|
||||
// sequence, after which the newly written file is no longer watched.
|
||||
err = watcher.WatchFlags(fileName, fsnotify.FSN_MODIFY)
|
||||
case err := <-watcher.Error:
|
||||
log.Errorln("Error watching config:", err)
|
||||
func getCache(cacheSize int, cacheType string, registerer prometheus.Registerer) (mapper.MetricMapperCache, error) {
|
||||
var cache mapper.MetricMapperCache
|
||||
var err error
|
||||
if cacheSize == 0 {
|
||||
return nil, nil
|
||||
} else {
|
||||
switch cacheType {
|
||||
case "lru":
|
||||
cache, err = lru.NewMetricMapperLRUCache(registerer, cacheSize)
|
||||
case "random":
|
||||
cache, err = randomreplacement.NewMetricMapperRRCache(registerer, cacheSize)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported cache type %q", cacheType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
var (
|
||||
listenAddress = kingpin.Flag("web.listen-address", "The address on which to expose the web interface and generated Prometheus metrics.").Default(":9102").String()
|
||||
enableLifecycle = kingpin.Flag("web.enable-lifecycle", "Enable shutdown and reload via HTTP request.").Default("false").Bool()
|
||||
metricsEndpoint = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String()
|
||||
statsdListenUDP = kingpin.Flag("statsd.listen-udp", "The UDP address on which to receive statsd metric lines. \"\" disables it.").Default(":9125").String()
|
||||
statsdListenTCP = kingpin.Flag("statsd.listen-tcp", "The TCP address on which to receive statsd metric lines. \"\" disables it.").Default(":9125").String()
|
||||
statsdListenUnixgram = kingpin.Flag("statsd.listen-unixgram", "The Unixgram socket path to receive statsd metric lines in datagram. \"\" disables it.").Default("").String()
|
||||
// not using Int here because flag displays default in decimal, 0755 will show as 493
|
||||
statsdUnixSocketMode = kingpin.Flag("statsd.unixsocket-mode", "The permission mode of the unix socket.").Default("755").String()
|
||||
mappingConfig = kingpin.Flag("statsd.mapping-config", "Metric mapping configuration file name.").String()
|
||||
readBuffer = kingpin.Flag("statsd.read-buffer", "Size (in bytes) of the operating system's transmit read buffer associated with the UDP or Unixgram connection. Please make sure the kernel parameters net.core.rmem_max is set to a value greater than the value specified.").Int()
|
||||
cacheSize = kingpin.Flag("statsd.cache-size", "Maximum size of your metric mapping cache. Relies on least recently used replacement policy if max size is reached.").Default("1000").Int()
|
||||
cacheType = kingpin.Flag("statsd.cache-type", "Metric mapping cache type. Valid options are \"lru\" and \"random\"").Default("lru").Enum("lru", "random")
|
||||
eventQueueSize = kingpin.Flag("statsd.event-queue-size", "Size of internal queue for processing events.").Default("10000").Uint()
|
||||
eventFlushThreshold = kingpin.Flag("statsd.event-flush-threshold", "Number of events to hold in queue before flushing.").Default("1000").Int()
|
||||
eventFlushInterval = kingpin.Flag("statsd.event-flush-interval", "Maximum time between event queue flushes.").Default("200ms").Duration()
|
||||
dumpFSMPath = kingpin.Flag("debug.dump-fsm", "The path to dump internal FSM generated for glob matching as Dot file.").Default("").String()
|
||||
checkConfig = kingpin.Flag("check-config", "Check configuration and exit.").Default("false").Bool()
|
||||
dogstatsdTagsEnabled = kingpin.Flag("statsd.parse-dogstatsd-tags", "Parse DogStatsd style tags. Enabled by default.").Default("true").Bool()
|
||||
influxdbTagsEnabled = kingpin.Flag("statsd.parse-influxdb-tags", "Parse InfluxDB style tags. Enabled by default.").Default("true").Bool()
|
||||
libratoTagsEnabled = kingpin.Flag("statsd.parse-librato-tags", "Parse Librato style tags. Enabled by default.").Default("true").Bool()
|
||||
signalFXTagsEnabled = kingpin.Flag("statsd.parse-signalfx-tags", "Parse SignalFX style tags. Enabled by default.").Default("true").Bool()
|
||||
relayAddr = kingpin.Flag("statsd.relay.address", "The UDP relay target address (host:port)").String()
|
||||
relayPacketLen = kingpin.Flag("statsd.relay.packet-length", "Maximum relay output packet length to avoid fragmentation").Default("1400").Uint()
|
||||
udpPacketQueueSize = kingpin.Flag("statsd.udp-packet-queue-size", "Size of internal queue for processing UDP packets.").Default("10000").Int()
|
||||
)
|
||||
|
||||
if *showVersion {
|
||||
fmt.Fprintln(os.Stdout, version.Print("statsd_exporter"))
|
||||
os.Exit(0)
|
||||
promlogConfig := &promlog.Config{}
|
||||
flag.AddFlags(kingpin.CommandLine, promlogConfig)
|
||||
kingpin.Version(version.Print("statsd_exporter"))
|
||||
kingpin.CommandLine.UsageWriter(os.Stdout)
|
||||
kingpin.HelpFlag.Short('h')
|
||||
kingpin.Parse()
|
||||
logger := promlog.New(promlogConfig)
|
||||
if err := level.SetLogLevel(promlogConfig.Level.String()); err != nil {
|
||||
level.Error(logger).Log("msg", "failed to set log level", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
prometheus.MustRegister(versioncollector.NewCollector("statsd_exporter"))
|
||||
|
||||
parser := line.NewParser()
|
||||
if *dogstatsdTagsEnabled {
|
||||
parser.EnableDogstatsdParsing()
|
||||
}
|
||||
if *influxdbTagsEnabled {
|
||||
parser.EnableInfluxdbParsing()
|
||||
}
|
||||
if *libratoTagsEnabled {
|
||||
parser.EnableLibratoParsing()
|
||||
}
|
||||
if *signalFXTagsEnabled {
|
||||
parser.EnableSignalFXParsing()
|
||||
}
|
||||
|
||||
if *addSuffix {
|
||||
log.Warnln("Warning: Using -statsd.add-suffix is discouraged. We recommend explicitly naming metrics appropriately in the mapping configuration.")
|
||||
}
|
||||
log.Infoln("Starting StatsD -> Prometheus Exporter", version.Info())
|
||||
log.Infoln("Build context", version.BuildContext())
|
||||
log.Infoln("Accepting StatsD Traffic on", *statsdListenAddress)
|
||||
log.Infoln("Accepting Prometheus Requests on", *listenAddress)
|
||||
level.Info(logger).Log("msg", "Starting StatsD -> Prometheus Exporter", "version", version.Info())
|
||||
level.Info(logger).Log("msg", "Build context", "context", version.BuildContext())
|
||||
|
||||
go serveHTTP()
|
||||
|
||||
events := make(chan Events, 1024)
|
||||
events := make(chan event.Events, *eventQueueSize)
|
||||
defer close(events)
|
||||
eventQueue := event.NewEventQueue(events, *eventFlushThreshold, *eventFlushInterval, eventsFlushed)
|
||||
|
||||
listenAddr := udpAddrFromString(*statsdListenAddress)
|
||||
conn, err := net.ListenUDP("udp", listenAddr)
|
||||
thisMapper := &mapper.MetricMapper{Registerer: prometheus.DefaultRegisterer, MappingsCount: mappingsCount, Logger: logger}
|
||||
|
||||
cache, err := getCache(*cacheSize, *cacheType, thisMapper.Registerer)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
level.Error(logger).Log("msg", "Unable to setup metric mapper cache", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
thisMapper.UseCache(cache)
|
||||
|
||||
if *readBuffer != 0 {
|
||||
err = conn.SetReadBuffer(*readBuffer)
|
||||
if err != nil {
|
||||
log.Fatal("Error setting UDP read buffer:", err)
|
||||
}
|
||||
}
|
||||
|
||||
l := &StatsDListener{conn: conn}
|
||||
go l.Listen(events)
|
||||
|
||||
mapper := &metricMapper{}
|
||||
if *mappingConfig != "" {
|
||||
err := mapper.initFromFile(*mappingConfig)
|
||||
err := thisMapper.InitFromFile(*mappingConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Error loading config:", err)
|
||||
level.Error(logger).Log("msg", "error loading config", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *dumpFSMPath != "" {
|
||||
err := dumpFSM(thisMapper, *dumpFSMPath, logger)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "error dumping FSM", "error", err)
|
||||
// Failure to dump the FSM is an error (the user asked for it and it
|
||||
// didn't happen) but not fatal (the exporter is fully functional
|
||||
// afterwards).
|
||||
}
|
||||
}
|
||||
go watchConfig(*mappingConfig, mapper)
|
||||
}
|
||||
exporter := NewExporter(mapper, *addSuffix)
|
||||
exporter.Listen(events)
|
||||
|
||||
exporter := exporter.NewExporter(prometheus.DefaultRegisterer, thisMapper, logger, eventsActions, eventsUnmapped, errorEventStats, eventStats, conflictingEventStats, metricsCount)
|
||||
|
||||
if *checkConfig {
|
||||
level.Info(logger).Log("msg", "Configuration check successful, exiting")
|
||||
return
|
||||
}
|
||||
|
||||
var relayTarget *relay.Relay
|
||||
if *relayAddr != "" {
|
||||
var err error
|
||||
relayTarget, err = relay.NewRelay(logger, *relayAddr, *relayPacketLen)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "Unable to create relay", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
level.Info(logger).Log("msg", "Accepting StatsD Traffic", "udp", *statsdListenUDP, "tcp", *statsdListenTCP, "unixgram", *statsdListenUnixgram)
|
||||
level.Info(logger).Log("msg", "Accepting Prometheus Requests", "addr", *listenAddress)
|
||||
|
||||
if *statsdListenUDP == "" && *statsdListenTCP == "" && *statsdListenUnixgram == "" {
|
||||
level.Error(logger).Log("At least one of UDP/TCP/Unixgram listeners must be specified.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *statsdListenUDP != "" {
|
||||
udpListenAddr, err := address.UDPAddrFromString(*statsdListenUDP)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "invalid UDP listen address", "address", *statsdListenUDP, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
uconn, err := net.ListenUDP("udp", udpListenAddr)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "failed to start UDP listener", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *readBuffer != 0 {
|
||||
err = uconn.SetReadBuffer(*readBuffer)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "error setting UDP read buffer", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
udpPacketQueue := make(chan []byte, *udpPacketQueueSize)
|
||||
|
||||
ul := &listener.StatsDUDPListener{
|
||||
Conn: uconn,
|
||||
EventHandler: eventQueue,
|
||||
Logger: logger,
|
||||
LineParser: parser,
|
||||
UDPPackets: udpPackets,
|
||||
UDPPacketDrops: udpPacketDrops,
|
||||
LinesReceived: linesReceived,
|
||||
EventsFlushed: eventsFlushed,
|
||||
Relay: relayTarget,
|
||||
SampleErrors: *sampleErrors,
|
||||
SamplesReceived: samplesReceived,
|
||||
TagErrors: tagErrors,
|
||||
TagsReceived: tagsReceived,
|
||||
UdpPacketQueue: udpPacketQueue,
|
||||
}
|
||||
|
||||
go ul.Listen()
|
||||
}
|
||||
|
||||
if *statsdListenTCP != "" {
|
||||
tcpListenAddr, err := address.TCPAddrFromString(*statsdListenTCP)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "invalid TCP listen address", "address", *statsdListenUDP, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
tconn, err := net.ListenTCP("tcp", tcpListenAddr)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer tconn.Close()
|
||||
|
||||
tl := &listener.StatsDTCPListener{
|
||||
Conn: tconn,
|
||||
EventHandler: eventQueue,
|
||||
Logger: logger,
|
||||
LineParser: parser,
|
||||
LinesReceived: linesReceived,
|
||||
EventsFlushed: eventsFlushed,
|
||||
Relay: relayTarget,
|
||||
SampleErrors: *sampleErrors,
|
||||
SamplesReceived: samplesReceived,
|
||||
TagErrors: tagErrors,
|
||||
TagsReceived: tagsReceived,
|
||||
TCPConnections: tcpConnections,
|
||||
TCPErrors: tcpErrors,
|
||||
TCPLineTooLong: tcpLineTooLong,
|
||||
}
|
||||
|
||||
go tl.Listen()
|
||||
}
|
||||
|
||||
if *statsdListenUnixgram != "" {
|
||||
var err error
|
||||
if _, err = os.Stat(*statsdListenUnixgram); !os.IsNotExist(err) {
|
||||
level.Error(logger).Log("msg", "Unixgram socket already exists", "socket_name", *statsdListenUnixgram)
|
||||
os.Exit(1)
|
||||
}
|
||||
uxgconn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{
|
||||
Net: "unixgram",
|
||||
Name: *statsdListenUnixgram,
|
||||
})
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "failed to listen on Unixgram socket", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer uxgconn.Close()
|
||||
|
||||
if *readBuffer != 0 {
|
||||
err = uxgconn.SetReadBuffer(*readBuffer)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "error setting Unixgram read buffer", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
ul := &listener.StatsDUnixgramListener{
|
||||
Conn: uxgconn,
|
||||
EventHandler: eventQueue,
|
||||
Logger: logger,
|
||||
LineParser: parser,
|
||||
UnixgramPackets: unixgramPackets,
|
||||
LinesReceived: linesReceived,
|
||||
EventsFlushed: eventsFlushed,
|
||||
Relay: relayTarget,
|
||||
SampleErrors: *sampleErrors,
|
||||
SamplesReceived: samplesReceived,
|
||||
TagErrors: tagErrors,
|
||||
TagsReceived: tagsReceived,
|
||||
}
|
||||
|
||||
go ul.Listen()
|
||||
|
||||
// if it's an abstract unix domain socket, it won't exist on fs
|
||||
// so we can't chmod it either
|
||||
if _, err := os.Stat(*statsdListenUnixgram); !os.IsNotExist(err) {
|
||||
defer os.Remove(*statsdListenUnixgram)
|
||||
|
||||
// convert the string to octet
|
||||
perm, err := strconv.ParseInt("0"+string(*statsdUnixSocketMode), 8, 32)
|
||||
if err != nil {
|
||||
level.Warn(logger).Log("Bad permission %s: %v, ignoring\n", *statsdUnixSocketMode, err)
|
||||
} else {
|
||||
err = os.Chmod(*statsdListenUnixgram, os.FileMode(perm))
|
||||
if err != nil {
|
||||
level.Warn(logger).Log("Failed to change unixgram socket permission: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mux := http.DefaultServeMux
|
||||
mux.Handle(*metricsEndpoint, promhttp.Handler())
|
||||
if *metricsEndpoint != "/" && *metricsEndpoint != "" {
|
||||
landingConfig := web.LandingConfig{
|
||||
Name: "StatsD Exporter",
|
||||
Description: "Prometheus Exporter for converting StatsD to Prometheus metrics",
|
||||
Version: version.Info(),
|
||||
Links: []web.LandingLinks{
|
||||
{
|
||||
Address: *metricsEndpoint,
|
||||
Text: "Metrics",
|
||||
},
|
||||
},
|
||||
}
|
||||
landingPage, err := web.NewLandingPage(landingConfig)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mux.Handle("/", landingPage)
|
||||
}
|
||||
|
||||
quitChan := make(chan struct{}, 1)
|
||||
|
||||
if *enableLifecycle {
|
||||
mux.HandleFunc("/-/reload", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPut || r.Method == http.MethodPost {
|
||||
fmt.Fprintf(w, "Requesting reload")
|
||||
if *mappingConfig == "" {
|
||||
level.Warn(logger).Log("msg", "Received lifecycle api reload but no mapping config to reload")
|
||||
return
|
||||
}
|
||||
level.Info(logger).Log("msg", "Received lifecycle api reload, attempting reload")
|
||||
reloadConfig(*mappingConfig, thisMapper, logger)
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/-/quit", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPut || r.Method == http.MethodPost {
|
||||
fmt.Fprintf(w, "Requesting termination... Goodbye!")
|
||||
quitChan <- struct{}{}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mux.HandleFunc("/-/healthy", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
level.Debug(logger).Log("msg", "Received health check")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Statsd Exporter is Healthy.\n")
|
||||
}
|
||||
})
|
||||
|
||||
mux.HandleFunc("/-/ready", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
level.Debug(logger).Log("msg", "Received ready check")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Statsd Exporter is Ready.\n")
|
||||
}
|
||||
})
|
||||
|
||||
go serveHTTP(mux, *listenAddress, logger)
|
||||
|
||||
go sighupConfigReloader(*mappingConfig, thisMapper, logger)
|
||||
go exporter.Listen(events)
|
||||
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// quit if we get a message on either channel
|
||||
select {
|
||||
case sig := <-signals:
|
||||
level.Info(logger).Log("msg", "Received os signal, exiting", "signal", sig.String())
|
||||
case <-quitChan:
|
||||
level.Info(logger).Log("msg", "Received lifecycle api quit, exiting")
|
||||
}
|
||||
}
|
||||
|
|
144
mapper.go
144
mapper.go
|
@ -1,144 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
identifierRE = `[a-zA-Z_][a-zA-Z0-9_]+`
|
||||
statsdMetricRE = `[a-zA-Z_](-?[a-zA-Z0-9_])+`
|
||||
|
||||
metricLineRE = regexp.MustCompile(`^(\*\.|` + statsdMetricRE + `\.)+(\*|` + statsdMetricRE + `)$`)
|
||||
labelLineRE = regexp.MustCompile(`^(` + identifierRE + `)\s*=\s*"(.*)"$`)
|
||||
metricNameRE = regexp.MustCompile(`^` + identifierRE + `$`)
|
||||
)
|
||||
|
||||
type metricMapping struct {
|
||||
regex *regexp.Regexp
|
||||
labels prometheus.Labels
|
||||
}
|
||||
|
||||
type metricMapper struct {
|
||||
mappings []metricMapping
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type configLoadStates int
|
||||
|
||||
const (
|
||||
SEARCHING configLoadStates = iota
|
||||
METRIC_DEFINITION
|
||||
)
|
||||
|
||||
func (m *metricMapper) initFromString(fileContents string) error {
|
||||
lines := strings.Split(fileContents, "\n")
|
||||
state := SEARCHING
|
||||
|
||||
parsedMappings := []metricMapping{}
|
||||
currentMapping := metricMapping{labels: prometheus.Labels{}}
|
||||
for i, line := range lines {
|
||||
line := strings.TrimSpace(line)
|
||||
|
||||
switch state {
|
||||
case SEARCHING:
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if !metricLineRE.MatchString(line) {
|
||||
return fmt.Errorf("Line %d: expected metric match line, got: %s", i, line)
|
||||
}
|
||||
|
||||
// Translate the glob-style metric match line into a proper regex that we
|
||||
// can use to match metrics later on.
|
||||
metricRe := strings.Replace(line, ".", "\\.", -1)
|
||||
metricRe = strings.Replace(metricRe, "*", "([^.]+)", -1)
|
||||
currentMapping.regex = regexp.MustCompile("^" + metricRe + "$")
|
||||
|
||||
state = METRIC_DEFINITION
|
||||
|
||||
case METRIC_DEFINITION:
|
||||
if line == "" {
|
||||
if len(currentMapping.labels) == 0 {
|
||||
return fmt.Errorf("Line %d: metric mapping didn't set any labels", i)
|
||||
}
|
||||
if _, ok := currentMapping.labels["name"]; !ok {
|
||||
return fmt.Errorf("Line %d: metric mapping didn't set a metric name", i)
|
||||
}
|
||||
|
||||
parsedMappings = append(parsedMappings, currentMapping)
|
||||
|
||||
state = SEARCHING
|
||||
currentMapping = metricMapping{labels: prometheus.Labels{}}
|
||||
continue
|
||||
}
|
||||
|
||||
matches := labelLineRE.FindStringSubmatch(line)
|
||||
if len(matches) != 3 {
|
||||
return fmt.Errorf("Line %d: expected label mapping line, got: %s", i, line)
|
||||
}
|
||||
label, value := matches[1], matches[2]
|
||||
if label == "name" && !metricNameRE.MatchString(value) {
|
||||
return fmt.Errorf("Line %d: metric name '%s' doesn't match regex '%s'", i, value, metricNameRE)
|
||||
}
|
||||
currentMapping.labels[label] = value
|
||||
default:
|
||||
panic("illegal state")
|
||||
}
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.mappings = parsedMappings
|
||||
|
||||
mappingsCount.Set(float64(len(parsedMappings)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *metricMapper) initFromFile(fileName string) error {
|
||||
mappingStr, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.initFromString(string(mappingStr))
|
||||
}
|
||||
|
||||
func (m *metricMapper) getMapping(statsdMetric string) (labels prometheus.Labels, present bool) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
for _, mapping := range m.mappings {
|
||||
matches := mapping.regex.FindStringSubmatchIndex(statsdMetric)
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
labels := prometheus.Labels{}
|
||||
for label, valueExpr := range mapping.labels {
|
||||
value := mapping.regex.ExpandString([]byte{}, valueExpr, statsdMetric, matches)
|
||||
labels[label] = string(value)
|
||||
}
|
||||
return labels, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
164
mapper_test.go
164
mapper_test.go
|
@ -1,164 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMetricMapper(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config string
|
||||
configBad bool
|
||||
mappings map[string]map[string]string
|
||||
}{
|
||||
// Empty config.
|
||||
{},
|
||||
// Config with several mapping definitions.
|
||||
{
|
||||
config: `
|
||||
test.dispatcher.*.*.*
|
||||
name="dispatch_events"
|
||||
processor="$1"
|
||||
action="$2"
|
||||
result="$3"
|
||||
job="test_dispatcher"
|
||||
|
||||
test.my-dispatch-host01.name.dispatcher.*.*.*
|
||||
name="host_dispatch_events"
|
||||
processor="$1"
|
||||
action="$2"
|
||||
result="$3"
|
||||
job="test_dispatcher"
|
||||
|
||||
*.*
|
||||
name="catchall"
|
||||
first="$1"
|
||||
second="$2"
|
||||
third="$3"
|
||||
job="$1-$2-$3"
|
||||
`,
|
||||
mappings: map[string]map[string]string{
|
||||
"test.dispatcher.FooProcessor.send.succeeded": map[string]string{
|
||||
"name": "dispatch_events",
|
||||
"processor": "FooProcessor",
|
||||
"action": "send",
|
||||
"result": "succeeded",
|
||||
"job": "test_dispatcher",
|
||||
},
|
||||
"test.my-dispatch-host01.name.dispatcher.FooProcessor.send.succeeded": map[string]string{
|
||||
"name": "host_dispatch_events",
|
||||
"processor": "FooProcessor",
|
||||
"action": "send",
|
||||
"result": "succeeded",
|
||||
"job": "test_dispatcher",
|
||||
},
|
||||
"foo.bar": map[string]string{
|
||||
"name": "catchall",
|
||||
"first": "foo",
|
||||
"second": "bar",
|
||||
"third": "",
|
||||
"job": "foo-bar-",
|
||||
},
|
||||
"foo.bar.baz": map[string]string{},
|
||||
},
|
||||
},
|
||||
// Config with bad regex reference.
|
||||
{
|
||||
config: `
|
||||
test.*
|
||||
name="name"
|
||||
label="$1_foo"
|
||||
`,
|
||||
mappings: map[string]map[string]string{
|
||||
"test.a": map[string]string{
|
||||
"name": "name",
|
||||
"label": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Config with good regex reference.
|
||||
{
|
||||
config: `
|
||||
test.*
|
||||
name="name"
|
||||
label="${1}_foo"
|
||||
`,
|
||||
mappings: map[string]map[string]string{
|
||||
"test.a": map[string]string{
|
||||
"name": "name",
|
||||
"label": "a_foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Config with bad metric line.
|
||||
{
|
||||
config: `
|
||||
bad--metric-line.*.*
|
||||
name="foo"
|
||||
`,
|
||||
configBad: true,
|
||||
},
|
||||
// Config with bad label line.
|
||||
{
|
||||
config: `
|
||||
test.*.*
|
||||
name=foo
|
||||
`,
|
||||
configBad: true,
|
||||
},
|
||||
// Config with bad label line.
|
||||
{
|
||||
config: `
|
||||
test.*.*
|
||||
name="foo-name"
|
||||
`,
|
||||
configBad: true,
|
||||
},
|
||||
// Config with bad metric name.
|
||||
{
|
||||
config: `
|
||||
test.*.*
|
||||
name="0foo"
|
||||
`,
|
||||
configBad: true,
|
||||
},
|
||||
}
|
||||
|
||||
mapper := metricMapper{}
|
||||
for i, scenario := range scenarios {
|
||||
err := mapper.initFromString(scenario.config)
|
||||
if err != nil && !scenario.configBad {
|
||||
t.Fatalf("%d. Config load error: %s", i, err)
|
||||
}
|
||||
if err == nil && scenario.configBad {
|
||||
t.Fatalf("%d. Expected bad config, but loaded ok", i)
|
||||
}
|
||||
|
||||
for metric, mapping := range scenario.mappings {
|
||||
labels, present := mapper.getMapping(metric)
|
||||
if len(labels) == 0 && present {
|
||||
t.Fatalf("%d.%q: Expected metric to not be present", i, metric)
|
||||
}
|
||||
if len(labels) != len(mapping) {
|
||||
t.Fatalf("%d.%q: Expected %d labels, got %d", i, metric, len(mapping), len(labels))
|
||||
}
|
||||
for label, value := range labels {
|
||||
if mapping[label] != value {
|
||||
t.Fatalf("%d.%q: Expected labels %v, got %v", i, metric, mapping, labels)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
pkg/README.md
Normal file
3
pkg/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
The `pkg` directory is deprecated.
|
||||
Please do not add new packages to this directory.
|
||||
Existing packages will be moved elsewhere eventually.
|
66
pkg/address/address.go
Normal file
66
pkg/address/address.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package address
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func IPPortFromString(addr string) (*net.IPAddr, int, error) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("bad StatsD listening address: %s", addr)
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
ip, err := net.ResolveIPAddr("ip", host)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("unable to resolve %s: %s", host, err)
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil || port < 0 || port > 65535 {
|
||||
return nil, 0, fmt.Errorf("bad port %s: %s", portStr, err)
|
||||
}
|
||||
|
||||
return ip, port, nil
|
||||
}
|
||||
|
||||
func UDPAddrFromString(addr string) (*net.UDPAddr, error) {
|
||||
ip, port, err := IPPortFromString(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &net.UDPAddr{
|
||||
IP: ip.IP,
|
||||
Port: port,
|
||||
Zone: ip.Zone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TCPAddrFromString(addr string) (*net.TCPAddr, error) {
|
||||
ip, port, err := IPPortFromString(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &net.TCPAddr{
|
||||
IP: ip.IP,
|
||||
Port: port,
|
||||
Zone: ip.Zone,
|
||||
}, nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 The Prometheus Authors
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
@ -11,26 +11,31 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Build only when actually fuzzing
|
||||
// +build gofuzz
|
||||
package clock
|
||||
|
||||
package expfmt
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
import "bytes"
|
||||
var ClockInstance *Clock
|
||||
|
||||
// Fuzz text metric parser with with github.com/dvyukov/go-fuzz:
|
||||
//
|
||||
// go-fuzz-build github.com/prometheus/client_golang/text
|
||||
// go-fuzz -bin text-fuzz.zip -workdir fuzz
|
||||
//
|
||||
// Further input samples should go in the folder fuzz/corpus.
|
||||
func Fuzz(in []byte) int {
|
||||
parser := TextParser{}
|
||||
_, err := parser.TextToMetricFamilies(bytes.NewReader(in))
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
type Clock struct {
|
||||
Instant time.Time
|
||||
TickerCh chan time.Time
|
||||
}
|
||||
|
||||
func Now() time.Time {
|
||||
if ClockInstance == nil {
|
||||
return time.Now()
|
||||
}
|
||||
return ClockInstance.Instant
|
||||
}
|
||||
|
||||
func NewTicker(d time.Duration) *time.Ticker {
|
||||
if ClockInstance == nil || ClockInstance.TickerCh == nil {
|
||||
return time.NewTicker(d)
|
||||
}
|
||||
return &time.Ticker{
|
||||
C: ClockInstance.TickerCh,
|
||||
}
|
||||
}
|
138
pkg/event/event.go
Normal file
138
pkg/event/event.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/statsd_exporter/pkg/clock"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper"
|
||||
)
|
||||
|
||||
type Event interface {
|
||||
MetricName() string
|
||||
Value() float64
|
||||
Labels() map[string]string
|
||||
MetricType() mapper.MetricType
|
||||
}
|
||||
|
||||
type CounterEvent struct {
|
||||
CMetricName string
|
||||
CValue float64
|
||||
CLabels map[string]string
|
||||
}
|
||||
|
||||
func (c *CounterEvent) MetricName() string { return c.CMetricName }
|
||||
func (c *CounterEvent) Value() float64 { return c.CValue }
|
||||
func (c *CounterEvent) Labels() map[string]string { return c.CLabels }
|
||||
func (c *CounterEvent) MetricType() mapper.MetricType { return mapper.MetricTypeCounter }
|
||||
|
||||
type GaugeEvent struct {
|
||||
GMetricName string
|
||||
GValue float64
|
||||
GRelative bool
|
||||
GLabels map[string]string
|
||||
}
|
||||
|
||||
func (g *GaugeEvent) MetricName() string { return g.GMetricName }
|
||||
func (g *GaugeEvent) Value() float64 { return g.GValue }
|
||||
func (g *GaugeEvent) Labels() map[string]string { return g.GLabels }
|
||||
func (g *GaugeEvent) MetricType() mapper.MetricType { return mapper.MetricTypeGauge }
|
||||
|
||||
type ObserverEvent struct {
|
||||
OMetricName string
|
||||
OValue float64
|
||||
OLabels map[string]string
|
||||
}
|
||||
|
||||
func (o *ObserverEvent) MetricName() string { return o.OMetricName }
|
||||
func (o *ObserverEvent) Value() float64 { return o.OValue }
|
||||
func (o *ObserverEvent) Labels() map[string]string { return o.OLabels }
|
||||
func (o *ObserverEvent) MetricType() mapper.MetricType { return mapper.MetricTypeObserver }
|
||||
|
||||
type Events []Event
|
||||
|
||||
type EventQueue struct {
|
||||
C chan Events
|
||||
q Events
|
||||
m sync.Mutex
|
||||
flushTicker *time.Ticker
|
||||
flushThreshold int
|
||||
flushInterval time.Duration
|
||||
eventsFlushed prometheus.Counter
|
||||
}
|
||||
|
||||
type EventHandler interface {
|
||||
Queue(event Events)
|
||||
}
|
||||
|
||||
func NewEventQueue(c chan Events, flushThreshold int, flushInterval time.Duration, eventsFlushed prometheus.Counter) *EventQueue {
|
||||
ticker := clock.NewTicker(flushInterval)
|
||||
eq := &EventQueue{
|
||||
C: c,
|
||||
flushThreshold: flushThreshold,
|
||||
flushInterval: flushInterval,
|
||||
flushTicker: ticker,
|
||||
q: make([]Event, 0, flushThreshold),
|
||||
eventsFlushed: eventsFlushed,
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
<-ticker.C
|
||||
eq.Flush()
|
||||
}
|
||||
}()
|
||||
return eq
|
||||
}
|
||||
|
||||
func (eq *EventQueue) Queue(events Events) {
|
||||
eq.m.Lock()
|
||||
defer eq.m.Unlock()
|
||||
|
||||
for _, e := range events {
|
||||
eq.q = append(eq.q, e)
|
||||
if len(eq.q) >= eq.flushThreshold {
|
||||
eq.FlushUnlocked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eq *EventQueue) Flush() {
|
||||
eq.m.Lock()
|
||||
defer eq.m.Unlock()
|
||||
eq.FlushUnlocked()
|
||||
}
|
||||
|
||||
func (eq *EventQueue) FlushUnlocked() {
|
||||
eq.C <- eq.q
|
||||
eq.q = make([]Event, 0, cap(eq.q))
|
||||
eq.eventsFlushed.Inc()
|
||||
}
|
||||
|
||||
func (eq *EventQueue) Len() int {
|
||||
eq.m.Lock()
|
||||
defer eq.m.Unlock()
|
||||
|
||||
return len(eq.q)
|
||||
}
|
||||
|
||||
type UnbufferedEventHandler struct {
|
||||
C chan Events
|
||||
}
|
||||
|
||||
func (ueh *UnbufferedEventHandler) Queue(events Events) {
|
||||
ueh.C <- events
|
||||
}
|
87
pkg/event/event_test.go
Normal file
87
pkg/event/event_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/statsd_exporter/pkg/clock"
|
||||
)
|
||||
|
||||
var eventsFlushed = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_event_queue_flushed_total",
|
||||
Help: "Number of times events were flushed to exporter",
|
||||
},
|
||||
)
|
||||
|
||||
func TestEventThresholdFlush(t *testing.T) {
|
||||
c := make(chan Events, 100)
|
||||
// We're not going to flush during this test, so the duration doesn't matter.
|
||||
eq := NewEventQueue(c, 5, time.Second, eventsFlushed)
|
||||
e := make(Events, 13)
|
||||
go func() {
|
||||
eq.Queue(e)
|
||||
}()
|
||||
|
||||
batch := <-c
|
||||
if len(batch) != 5 {
|
||||
t.Fatalf("Expected event batch to be 5 elements, but got %v", len(batch))
|
||||
}
|
||||
batch = <-c
|
||||
if len(batch) != 5 {
|
||||
t.Fatalf("Expected event batch to be 5 elements, but got %v", len(batch))
|
||||
}
|
||||
batch = <-c
|
||||
if len(batch) != 3 {
|
||||
t.Fatalf("Expected event batch to be 3 elements, but got %v", len(batch))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventIntervalFlush(t *testing.T) {
|
||||
// Mock a time.NewTicker
|
||||
tickerCh := make(chan time.Time)
|
||||
clock.ClockInstance = &clock.Clock{
|
||||
TickerCh: tickerCh,
|
||||
}
|
||||
clock.ClockInstance.Instant = time.Unix(0, 0)
|
||||
|
||||
c := make(chan Events, 100)
|
||||
eq := NewEventQueue(c, 1000, time.Second*1000, eventsFlushed)
|
||||
e := make(Events, 10)
|
||||
eq.Queue(e)
|
||||
|
||||
if eq.Len() != 10 {
|
||||
t.Fatal("Expected 10 events to be queued, but got", eq.Len())
|
||||
}
|
||||
|
||||
if len(eq.C) != 0 {
|
||||
t.Fatal("Expected 0 events in the event channel, but got", len(eq.C))
|
||||
}
|
||||
|
||||
// Tick time forward to trigger a flush
|
||||
clock.ClockInstance.Instant = time.Unix(10000, 0)
|
||||
clock.ClockInstance.TickerCh <- time.Unix(10000, 0)
|
||||
|
||||
events := <-eq.C
|
||||
if eq.Len() != 0 {
|
||||
t.Fatal("Expected 0 events to be queued, but got", eq.Len())
|
||||
}
|
||||
|
||||
if len(events) != 10 {
|
||||
t.Fatal("Expected 10 events in the event channel, but got", len(events))
|
||||
}
|
||||
}
|
212
pkg/exporter/exporter.go
Normal file
212
pkg/exporter/exporter.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package exporter
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/clock"
|
||||
"github.com/prometheus/statsd_exporter/pkg/event"
|
||||
"github.com/prometheus/statsd_exporter/pkg/level"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper"
|
||||
"github.com/prometheus/statsd_exporter/pkg/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHelp = "Metric autogenerated by statsd_exporter."
|
||||
regErrF = "Failed to update metric"
|
||||
)
|
||||
|
||||
type Registry interface {
|
||||
GetCounter(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Counter, error)
|
||||
GetGauge(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Gauge, error)
|
||||
GetHistogram(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Observer, error)
|
||||
GetSummary(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Observer, error)
|
||||
RemoveStaleMetrics()
|
||||
}
|
||||
|
||||
type Exporter struct {
|
||||
Mapper *mapper.MetricMapper
|
||||
Registry Registry
|
||||
Logger log.Logger
|
||||
EventsActions *prometheus.CounterVec
|
||||
EventsUnmapped prometheus.Counter
|
||||
ErrorEventStats *prometheus.CounterVec
|
||||
EventStats *prometheus.CounterVec
|
||||
ConflictingEventStats *prometheus.CounterVec
|
||||
MetricsCount *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// Listen handles all events sent to the given channel sequentially. It
|
||||
// terminates when the channel is closed.
|
||||
func (b *Exporter) Listen(e <-chan event.Events) {
|
||||
removeStaleMetricsTicker := clock.NewTicker(time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-removeStaleMetricsTicker.C:
|
||||
b.Registry.RemoveStaleMetrics()
|
||||
case events, ok := <-e:
|
||||
if !ok {
|
||||
level.Debug(b.Logger).Log("msg", "Channel is closed. Break out of Exporter.Listener.")
|
||||
removeStaleMetricsTicker.Stop()
|
||||
return
|
||||
}
|
||||
for _, event := range events {
|
||||
b.handleEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleEvent processes a single Event according to the configured mapping.
|
||||
func (b *Exporter) handleEvent(thisEvent event.Event) {
|
||||
mapping, labels, present := b.Mapper.GetMapping(thisEvent.MetricName(), thisEvent.MetricType())
|
||||
if mapping == nil {
|
||||
mapping = &mapper.MetricMapping{}
|
||||
if b.Mapper.Defaults.Ttl != 0 {
|
||||
mapping.Ttl = b.Mapper.Defaults.Ttl
|
||||
}
|
||||
}
|
||||
|
||||
if mapping.Action == mapper.ActionTypeDrop {
|
||||
b.EventsActions.WithLabelValues("drop").Inc()
|
||||
return
|
||||
}
|
||||
|
||||
metricName := ""
|
||||
|
||||
help := defaultHelp
|
||||
if mapping.HelpText != "" {
|
||||
help = mapping.HelpText
|
||||
}
|
||||
|
||||
prometheusLabels := thisEvent.Labels()
|
||||
if present {
|
||||
if mapping.Name == "" {
|
||||
level.Debug(b.Logger).Log("msg", "The mapping generates an empty metric name", "metric_name", thisEvent.MetricName(), "match", mapping.Match)
|
||||
b.ErrorEventStats.WithLabelValues("empty_metric_name").Inc()
|
||||
return
|
||||
}
|
||||
metricName = mapper.EscapeMetricName(mapping.Name)
|
||||
for label, value := range labels {
|
||||
if _, ok := prometheusLabels[label]; mapping.HonorLabels && ok {
|
||||
continue
|
||||
}
|
||||
|
||||
prometheusLabels[label] = value
|
||||
}
|
||||
b.EventsActions.WithLabelValues(string(mapping.Action)).Inc()
|
||||
} else {
|
||||
b.EventsUnmapped.Inc()
|
||||
metricName = mapper.EscapeMetricName(thisEvent.MetricName())
|
||||
}
|
||||
|
||||
eventValue := thisEvent.Value()
|
||||
if mapping.Scale.Set {
|
||||
eventValue *= mapping.Scale.Val
|
||||
}
|
||||
|
||||
switch ev := thisEvent.(type) {
|
||||
case *event.CounterEvent:
|
||||
// We don't accept negative values for counters. Incrementing the counter with a negative number
|
||||
// will cause the exporter to panic. Instead we will warn and continue to the next event.
|
||||
if eventValue < 0.0 {
|
||||
level.Debug(b.Logger).Log("msg", "counter must be non-negative value", "metric", metricName, "event_value", eventValue)
|
||||
b.ErrorEventStats.WithLabelValues("illegal_negative_counter").Inc()
|
||||
return
|
||||
}
|
||||
|
||||
counter, err := b.Registry.GetCounter(metricName, prometheusLabels, help, mapping, b.MetricsCount)
|
||||
if err == nil {
|
||||
counter.Add(eventValue)
|
||||
b.EventStats.WithLabelValues("counter").Inc()
|
||||
} else {
|
||||
level.Debug(b.Logger).Log("msg", regErrF, "metric", metricName, "error", err)
|
||||
b.ConflictingEventStats.WithLabelValues("counter").Inc()
|
||||
}
|
||||
|
||||
case *event.GaugeEvent:
|
||||
gauge, err := b.Registry.GetGauge(metricName, prometheusLabels, help, mapping, b.MetricsCount)
|
||||
|
||||
if err == nil {
|
||||
if ev.GRelative {
|
||||
gauge.Add(eventValue)
|
||||
} else {
|
||||
gauge.Set(eventValue)
|
||||
}
|
||||
b.EventStats.WithLabelValues("gauge").Inc()
|
||||
} else {
|
||||
level.Debug(b.Logger).Log("msg", regErrF, "metric", metricName, "error", err)
|
||||
b.ConflictingEventStats.WithLabelValues("gauge").Inc()
|
||||
}
|
||||
|
||||
case *event.ObserverEvent:
|
||||
t := mapper.ObserverTypeDefault
|
||||
if mapping != nil {
|
||||
t = mapping.ObserverType
|
||||
}
|
||||
if t == mapper.ObserverTypeDefault {
|
||||
t = b.Mapper.Defaults.ObserverType
|
||||
}
|
||||
|
||||
switch t {
|
||||
case mapper.ObserverTypeHistogram:
|
||||
histogram, err := b.Registry.GetHistogram(metricName, prometheusLabels, help, mapping, b.MetricsCount)
|
||||
if err == nil {
|
||||
histogram.Observe(eventValue)
|
||||
b.EventStats.WithLabelValues("observer").Inc()
|
||||
} else {
|
||||
level.Debug(b.Logger).Log("msg", regErrF, "metric", metricName, "error", err)
|
||||
b.ConflictingEventStats.WithLabelValues("observer").Inc()
|
||||
}
|
||||
|
||||
case mapper.ObserverTypeDefault, mapper.ObserverTypeSummary:
|
||||
summary, err := b.Registry.GetSummary(metricName, prometheusLabels, help, mapping, b.MetricsCount)
|
||||
if err == nil {
|
||||
summary.Observe(eventValue)
|
||||
b.EventStats.WithLabelValues("observer").Inc()
|
||||
} else {
|
||||
level.Debug(b.Logger).Log("msg", regErrF, "metric", metricName, "error", err)
|
||||
b.ConflictingEventStats.WithLabelValues("observer").Inc()
|
||||
}
|
||||
|
||||
default:
|
||||
level.Error(b.Logger).Log("msg", "unknown observer type", "type", t)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
default:
|
||||
level.Debug(b.Logger).Log("msg", "Unsupported event type")
|
||||
b.EventStats.WithLabelValues("illegal").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func NewExporter(reg prometheus.Registerer, mapper *mapper.MetricMapper, logger log.Logger, eventsActions *prometheus.CounterVec, eventsUnmapped prometheus.Counter, errorEventStats *prometheus.CounterVec, eventStats *prometheus.CounterVec, conflictingEventStats *prometheus.CounterVec, metricsCount *prometheus.GaugeVec) *Exporter {
|
||||
return &Exporter{
|
||||
Mapper: mapper,
|
||||
Registry: registry.NewRegistry(reg, mapper),
|
||||
Logger: logger,
|
||||
EventsActions: eventsActions,
|
||||
EventsUnmapped: eventsUnmapped,
|
||||
ErrorEventStats: errorEventStats,
|
||||
EventStats: eventStats,
|
||||
ConflictingEventStats: conflictingEventStats,
|
||||
MetricsCount: metricsCount,
|
||||
}
|
||||
}
|
1339
pkg/exporter/exporter_test.go
Normal file
1339
pkg/exporter/exporter_test.go
Normal file
File diff suppressed because it is too large
Load diff
97
pkg/level/level.go
Normal file
97
pkg/level/level.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package level
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
)
|
||||
|
||||
var logLevel = LevelInfo
|
||||
|
||||
// A Level is a logging priority. Higher levels are more important.
|
||||
type Level int
|
||||
|
||||
const (
|
||||
// LevelDebug logs are typically voluminous, and are usually disabled in
|
||||
// production.
|
||||
LevelDebug Level = iota
|
||||
// LevelInfo is the default logging priority.
|
||||
LevelInfo
|
||||
// LevelWarn logs are more important than Info, but don't need individual
|
||||
// human review.
|
||||
LevelWarn
|
||||
// LevelError logs are high-priority. If an application is running smoothly,
|
||||
// it shouldn't generate any error-level logs.
|
||||
LevelError
|
||||
)
|
||||
|
||||
var emptyLogger = &EmptyLogger{}
|
||||
|
||||
type EmptyLogger struct{}
|
||||
|
||||
func (l *EmptyLogger) Log(keyvals ...interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLogLevel sets the log level.
|
||||
func SetLogLevel(level string) error {
|
||||
switch level {
|
||||
case "debug":
|
||||
logLevel = LevelDebug
|
||||
case "info":
|
||||
logLevel = LevelInfo
|
||||
case "warn":
|
||||
logLevel = LevelWarn
|
||||
case "error":
|
||||
logLevel = LevelError
|
||||
default:
|
||||
return fmt.Errorf("unrecognized log level %s", level)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Error returns a logger that includes a Key/ErrorValue pair.
|
||||
func Error(logger log.Logger) log.Logger {
|
||||
if logLevel <= LevelError {
|
||||
return level.Error(logger)
|
||||
}
|
||||
return emptyLogger
|
||||
}
|
||||
|
||||
// Warn returns a logger that includes a Key/WarnValue pair.
|
||||
func Warn(logger log.Logger) log.Logger {
|
||||
if logLevel <= LevelWarn {
|
||||
return level.Warn(logger)
|
||||
}
|
||||
return emptyLogger
|
||||
}
|
||||
|
||||
// Info returns a logger that includes a Key/InfoValue pair.
|
||||
func Info(logger log.Logger) log.Logger {
|
||||
if logLevel <= LevelInfo {
|
||||
return level.Info(logger)
|
||||
}
|
||||
return emptyLogger
|
||||
}
|
||||
|
||||
// Debug returns a logger that includes a Key/DebugValue pair.
|
||||
func Debug(logger log.Logger) log.Logger {
|
||||
if logLevel <= LevelDebug {
|
||||
return level.Debug(logger)
|
||||
}
|
||||
return emptyLogger
|
||||
}
|
110
pkg/level/level_test.go
Normal file
110
pkg/level/level_test.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package level
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
)
|
||||
|
||||
func TestSetLogLevel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
level string
|
||||
logLevel Level
|
||||
wantErr bool
|
||||
}{
|
||||
{"wrong level", "foo", LevelInfo, true},
|
||||
{"level debug", "debug", LevelDebug, false},
|
||||
{"level info", "info", LevelInfo, false},
|
||||
{"level warn", "warn", LevelWarn, false},
|
||||
{"level error", "error", LevelError, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := SetLogLevel(tt.level); (err != nil) != tt.wantErr {
|
||||
t.Fatalf("Expected log level to be set successfully, but got %v", err)
|
||||
}
|
||||
if tt.logLevel != logLevel {
|
||||
t.Fatalf("Expected log level %v, but got %v", tt.logLevel, logLevel)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariousLevels(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
level string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
"level debug",
|
||||
"debug",
|
||||
strings.Join([]string{
|
||||
"level=debug log=debug",
|
||||
"level=info log=info",
|
||||
"level=warn log=warn",
|
||||
"level=error log=error",
|
||||
}, "\n"),
|
||||
},
|
||||
{
|
||||
"level info",
|
||||
"info",
|
||||
strings.Join([]string{
|
||||
"level=info log=info",
|
||||
"level=warn log=warn",
|
||||
"level=error log=error",
|
||||
}, "\n"),
|
||||
},
|
||||
{
|
||||
"level warn",
|
||||
"warn",
|
||||
strings.Join([]string{
|
||||
"level=warn log=warn",
|
||||
"level=error log=error",
|
||||
}, "\n"),
|
||||
},
|
||||
{
|
||||
"level error",
|
||||
"error",
|
||||
strings.Join([]string{
|
||||
"level=error log=error",
|
||||
}, "\n"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
logger := log.NewLogfmtLogger(&buf)
|
||||
|
||||
if err := SetLogLevel(tt.level); err != nil {
|
||||
t.Fatalf("Expected log level to be set successfully, but got %v", err)
|
||||
}
|
||||
|
||||
Debug(logger).Log("log", "debug")
|
||||
Info(logger).Log("log", "info")
|
||||
Warn(logger).Log("log", "warn")
|
||||
Error(logger).Log("log", "error")
|
||||
|
||||
got := strings.TrimSpace(buf.String())
|
||||
if tt.want != got {
|
||||
t.Fatalf("Expected log output %v, but got %v", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
342
pkg/line/line.go
Normal file
342
pkg/line/line.go
Normal file
|
@ -0,0 +1,342 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package line
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/event"
|
||||
"github.com/prometheus/statsd_exporter/pkg/level"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper"
|
||||
)
|
||||
|
||||
// Parser is a struct to hold configuration for parsing behavior
|
||||
type Parser struct {
|
||||
DogstatsdTagsEnabled bool
|
||||
InfluxdbTagsEnabled bool
|
||||
LibratoTagsEnabled bool
|
||||
SignalFXTagsEnabled bool
|
||||
}
|
||||
|
||||
// NewParser returns a new line parser
|
||||
func NewParser() *Parser {
|
||||
p := Parser{}
|
||||
return &p
|
||||
}
|
||||
|
||||
// EnableDogstatsdParsing option to enable dogstatsd tag parsing
|
||||
func (p *Parser) EnableDogstatsdParsing() {
|
||||
p.DogstatsdTagsEnabled = true
|
||||
}
|
||||
|
||||
// EnableInfluxdbParsing option to enable influxdb tag parsing
|
||||
func (p *Parser) EnableInfluxdbParsing() {
|
||||
p.InfluxdbTagsEnabled = true
|
||||
}
|
||||
|
||||
// EnableLibratoParsing option to enable librato tag parsing
|
||||
func (p *Parser) EnableLibratoParsing() {
|
||||
p.LibratoTagsEnabled = true
|
||||
}
|
||||
|
||||
// EnableSignalFXParsing option to enable signalfx tag parsing
|
||||
func (p *Parser) EnableSignalFXParsing() {
|
||||
p.SignalFXTagsEnabled = true
|
||||
}
|
||||
|
||||
func buildEvent(statType, metric string, value float64, relative bool, labels map[string]string) (event.Event, error) {
|
||||
switch statType {
|
||||
case "c":
|
||||
return &event.CounterEvent{
|
||||
CMetricName: metric,
|
||||
CValue: float64(value),
|
||||
CLabels: labels,
|
||||
}, nil
|
||||
case "g":
|
||||
return &event.GaugeEvent{
|
||||
GMetricName: metric,
|
||||
GValue: float64(value),
|
||||
GRelative: relative,
|
||||
GLabels: labels,
|
||||
}, nil
|
||||
case "ms":
|
||||
return &event.ObserverEvent{
|
||||
OMetricName: metric,
|
||||
OValue: float64(value) / 1000, // prometheus presumes seconds, statsd millisecond
|
||||
OLabels: labels,
|
||||
}, nil
|
||||
case "h", "d":
|
||||
return &event.ObserverEvent{
|
||||
OMetricName: metric,
|
||||
OValue: float64(value),
|
||||
OLabels: labels,
|
||||
}, nil
|
||||
case "s":
|
||||
return nil, fmt.Errorf("no support for StatsD sets")
|
||||
default:
|
||||
return nil, fmt.Errorf("bad stat type %s", statType)
|
||||
}
|
||||
}
|
||||
|
||||
func parseTag(component, tag string, separator rune, labels map[string]string, tagErrors prometheus.Counter, logger log.Logger) {
|
||||
// Entirely empty tag is an error
|
||||
if len(tag) == 0 {
|
||||
tagErrors.Inc()
|
||||
level.Debug(logger).Log("msg", "Empty name tag", "component", component)
|
||||
return
|
||||
}
|
||||
|
||||
for i, c := range tag {
|
||||
if c == separator {
|
||||
k := tag[:i]
|
||||
v := tag[i+1:]
|
||||
|
||||
if len(k) == 0 || len(v) == 0 {
|
||||
// Empty key or value is an error
|
||||
tagErrors.Inc()
|
||||
level.Debug(logger).Log("msg", "Malformed name tag", "k", k, "v", v, "component", component)
|
||||
} else {
|
||||
labels[mapper.EscapeMetricName(k)] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Missing separator (no value) is an error
|
||||
tagErrors.Inc()
|
||||
level.Debug(logger).Log("msg", "Malformed name tag", "tag", tag, "component", component)
|
||||
}
|
||||
|
||||
func parseNameTags(component string, labels map[string]string, tagErrors prometheus.Counter, logger log.Logger) {
|
||||
lastTagEndIndex := 0
|
||||
for i, c := range component {
|
||||
if c == ',' {
|
||||
tag := component[lastTagEndIndex:i]
|
||||
lastTagEndIndex = i + 1
|
||||
parseTag(component, tag, '=', labels, tagErrors, logger)
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not off the end of the string, add the last tag
|
||||
if lastTagEndIndex < len(component) {
|
||||
tag := component[lastTagEndIndex:]
|
||||
parseTag(component, tag, '=', labels, tagErrors, logger)
|
||||
}
|
||||
}
|
||||
|
||||
func trimLeftHash(s string) string {
|
||||
if s != "" && s[0] == '#' {
|
||||
return s[1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *Parser) ParseDogStatsDTags(component string, labels map[string]string, tagErrors prometheus.Counter, logger log.Logger) {
|
||||
if p.DogstatsdTagsEnabled {
|
||||
lastTagEndIndex := 0
|
||||
for i, c := range component {
|
||||
if c == ',' {
|
||||
tag := component[lastTagEndIndex:i]
|
||||
lastTagEndIndex = i + 1
|
||||
parseTag(component, trimLeftHash(tag), ':', labels, tagErrors, logger)
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not off the end of the string, add the last tag
|
||||
if lastTagEndIndex < len(component) {
|
||||
tag := component[lastTagEndIndex:]
|
||||
parseTag(component, trimLeftHash(tag), ':', labels, tagErrors, logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseNameAndTags(name string, labels map[string]string, tagErrors prometheus.Counter, logger log.Logger) string {
|
||||
if p.SignalFXTagsEnabled {
|
||||
// check for SignalFx tags first
|
||||
// `[` delimits start of tags by SignalFx
|
||||
// `]` delimits end of tags by SignalFx
|
||||
// https://docs.signalfx.com/en/latest/integrations/agent/monitors/collectd-statsd.html
|
||||
startIdx := strings.IndexRune(name, '[')
|
||||
endIdx := strings.IndexRune(name, ']')
|
||||
|
||||
switch {
|
||||
case startIdx != -1 && endIdx != -1:
|
||||
// good signalfx tags
|
||||
parseNameTags(name[startIdx+1:endIdx], labels, tagErrors, logger)
|
||||
return name[:startIdx] + name[endIdx+1:]
|
||||
case (startIdx != -1) != (endIdx != -1):
|
||||
// only one bracket, return unparsed
|
||||
level.Debug(logger).Log("msg", "invalid SignalFx tags, not parsing", "metric", name)
|
||||
tagErrors.Inc()
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
for i, c := range name {
|
||||
// `#` delimits start of tags by Librato
|
||||
// https://www.librato.com/docs/kb/collect/collection_agents/stastd/#stat-level-tags
|
||||
// `,` delimits start of tags by InfluxDB
|
||||
// https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/#introducing-influx-statsd
|
||||
if (c == '#' && p.LibratoTagsEnabled) || (c == ',' && p.InfluxdbTagsEnabled) {
|
||||
parseNameTags(name[i+1:], labels, tagErrors, logger)
|
||||
return name[:i]
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (p *Parser) LineToEvents(line string, sampleErrors prometheus.CounterVec, samplesReceived prometheus.Counter, tagErrors prometheus.Counter, tagsReceived prometheus.Counter, logger log.Logger) event.Events {
|
||||
events := event.Events{}
|
||||
if line == "" {
|
||||
return events
|
||||
}
|
||||
|
||||
elements := strings.SplitN(line, ":", 2)
|
||||
if len(elements) < 2 || len(elements[0]) == 0 || !utf8.ValidString(line) {
|
||||
sampleErrors.WithLabelValues("malformed_line").Inc()
|
||||
level.Debug(logger).Log("msg", "Bad line from StatsD", "line", line)
|
||||
return events
|
||||
}
|
||||
|
||||
labels := map[string]string{}
|
||||
metric := p.parseNameAndTags(elements[0], labels, tagErrors, logger)
|
||||
usingDogStatsDTags := strings.Contains(elements[1], "|#")
|
||||
if usingDogStatsDTags && len(labels) > 0 {
|
||||
// using DogStatsD tags
|
||||
|
||||
// don't allow mixed tagging styles
|
||||
sampleErrors.WithLabelValues("mixed_tagging_styles").Inc()
|
||||
level.Debug(logger).Log("msg", "Bad line (multiple tagging styles) from StatsD", "line", line)
|
||||
return events
|
||||
}
|
||||
|
||||
var samples []string
|
||||
lineParts := strings.SplitN(elements[1], "|", 3)
|
||||
if strings.Contains(lineParts[0], ":") {
|
||||
// handle DogStatsD extended aggregation
|
||||
isValidAggType := false
|
||||
switch lineParts[1] {
|
||||
case
|
||||
"ms", // timer
|
||||
"h", // histogram
|
||||
"d": // distribution
|
||||
isValidAggType = true
|
||||
}
|
||||
|
||||
if isValidAggType {
|
||||
aggValues := strings.Split(lineParts[0], ":")
|
||||
aggLines := make([]string, len(aggValues))
|
||||
_, aggLineSuffix, _ := strings.Cut(elements[1], "|")
|
||||
|
||||
for i, aggValue := range aggValues {
|
||||
aggLines[i] = strings.Join([]string{aggValue, aggLineSuffix}, "|")
|
||||
}
|
||||
samples = aggLines
|
||||
} else {
|
||||
sampleErrors.WithLabelValues("invalid_extended_aggregate_type").Inc()
|
||||
level.Debug(logger).Log("msg", "Bad line (invalid extended aggregate type) from StatsD", "line", line)
|
||||
return events
|
||||
}
|
||||
} else if usingDogStatsDTags {
|
||||
// disable multi-metrics
|
||||
samples = elements[1:]
|
||||
} else {
|
||||
samples = strings.Split(elements[1], ":")
|
||||
}
|
||||
|
||||
samples:
|
||||
for _, sample := range samples {
|
||||
samplesReceived.Inc()
|
||||
components := strings.Split(sample, "|")
|
||||
if len(components) < 2 || len(components) > 4 {
|
||||
sampleErrors.WithLabelValues("malformed_component").Inc()
|
||||
level.Debug(logger).Log("msg", "Bad component", "line", line)
|
||||
continue
|
||||
}
|
||||
valueStr, statType := components[0], components[1]
|
||||
|
||||
var relative = false
|
||||
if strings.Index(valueStr, "+") == 0 || strings.Index(valueStr, "-") == 0 {
|
||||
relative = true
|
||||
}
|
||||
|
||||
value, err := strconv.ParseFloat(valueStr, 64)
|
||||
if err != nil {
|
||||
level.Debug(logger).Log("msg", "Bad value", "value", valueStr, "line", line)
|
||||
sampleErrors.WithLabelValues("malformed_value").Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
multiplyEvents := 1
|
||||
if len(components) >= 3 {
|
||||
for _, component := range components[2:] {
|
||||
if len(component) == 0 {
|
||||
level.Debug(logger).Log("msg", "Empty component", "line", line)
|
||||
sampleErrors.WithLabelValues("malformed_component").Inc()
|
||||
continue samples
|
||||
}
|
||||
}
|
||||
|
||||
for _, component := range components[2:] {
|
||||
switch component[0] {
|
||||
case '@':
|
||||
|
||||
samplingFactor, err := strconv.ParseFloat(component[1:], 64)
|
||||
if err != nil {
|
||||
level.Debug(logger).Log("msg", "Invalid sampling factor", "component", component[1:], "line", line)
|
||||
sampleErrors.WithLabelValues("invalid_sample_factor").Inc()
|
||||
}
|
||||
if samplingFactor == 0 {
|
||||
samplingFactor = 1
|
||||
}
|
||||
|
||||
if statType == "g" {
|
||||
continue
|
||||
} else if statType == "c" {
|
||||
value /= samplingFactor
|
||||
} else if statType == "ms" || statType == "h" || statType == "d" {
|
||||
multiplyEvents = int(1 / samplingFactor)
|
||||
}
|
||||
case '#':
|
||||
p.ParseDogStatsDTags(component[1:], labels, tagErrors, logger)
|
||||
default:
|
||||
level.Debug(logger).Log("msg", "Invalid sampling factor or tag section", "component", components[2], "line", line)
|
||||
sampleErrors.WithLabelValues("invalid_sample_factor").Inc()
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(labels) > 0 {
|
||||
tagsReceived.Inc()
|
||||
}
|
||||
|
||||
for i := 0; i < multiplyEvents; i++ {
|
||||
event, err := buildEvent(statType, metric, value, relative, labels)
|
||||
if err != nil {
|
||||
level.Debug(logger).Log("msg", "Error building event", "line", line, "error", err)
|
||||
sampleErrors.WithLabelValues("illegal_event").Inc()
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
3507
pkg/line/line_test.go
Normal file
3507
pkg/line/line_test.go
Normal file
File diff suppressed because it is too large
Load diff
219
pkg/listener/listener.go
Normal file
219
pkg/listener/listener.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package listener
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/event"
|
||||
"github.com/prometheus/statsd_exporter/pkg/level"
|
||||
"github.com/prometheus/statsd_exporter/pkg/relay"
|
||||
)
|
||||
|
||||
type Parser interface {
|
||||
LineToEvents(line string, sampleErrors prometheus.CounterVec, samplesReceived prometheus.Counter, tagErrors prometheus.Counter, tagsReceived prometheus.Counter, logger log.Logger) event.Events
|
||||
}
|
||||
|
||||
type StatsDUDPListener struct {
|
||||
Conn *net.UDPConn
|
||||
EventHandler event.EventHandler
|
||||
Logger log.Logger
|
||||
LineParser Parser
|
||||
UDPPackets prometheus.Counter
|
||||
UDPPacketDrops prometheus.Counter
|
||||
LinesReceived prometheus.Counter
|
||||
EventsFlushed prometheus.Counter
|
||||
Relay *relay.Relay
|
||||
SampleErrors prometheus.CounterVec
|
||||
SamplesReceived prometheus.Counter
|
||||
TagErrors prometheus.Counter
|
||||
TagsReceived prometheus.Counter
|
||||
UdpPacketQueue chan []byte
|
||||
}
|
||||
|
||||
func (l *StatsDUDPListener) SetEventHandler(eh event.EventHandler) {
|
||||
l.EventHandler = eh
|
||||
}
|
||||
|
||||
func (l *StatsDUDPListener) Listen() {
|
||||
buf := make([]byte, 65535)
|
||||
go l.ProcessUdpPacketQueue()
|
||||
for {
|
||||
n, _, err := l.Conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
// https://github.com/golang/go/issues/4373
|
||||
// ignore net: errClosing error as it will occur during shutdown
|
||||
if strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||
return
|
||||
}
|
||||
level.Error(l.Logger).Log("error", err)
|
||||
return
|
||||
}
|
||||
|
||||
l.EnqueueUdpPacket(buf, n)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StatsDUDPListener) EnqueueUdpPacket(packet []byte, n int) {
|
||||
l.UDPPackets.Inc()
|
||||
packetCopy := make([]byte, n)
|
||||
copy(packetCopy, packet)
|
||||
select {
|
||||
case l.UdpPacketQueue <- packetCopy:
|
||||
// do nothing
|
||||
default:
|
||||
l.UDPPacketDrops.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StatsDUDPListener) ProcessUdpPacketQueue() {
|
||||
for {
|
||||
packet := <-l.UdpPacketQueue
|
||||
l.HandlePacket(packet)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StatsDUDPListener) HandlePacket(packet []byte) {
|
||||
lines := strings.Split(string(packet), "\n")
|
||||
for _, line := range lines {
|
||||
level.Debug(l.Logger).Log("msg", "Incoming line", "proto", "udp", "line", line)
|
||||
l.LinesReceived.Inc()
|
||||
if l.Relay != nil && len(line) > 0 {
|
||||
l.Relay.RelayLine(line)
|
||||
}
|
||||
l.EventHandler.Queue(l.LineParser.LineToEvents(line, l.SampleErrors, l.SamplesReceived, l.TagErrors, l.TagsReceived, l.Logger))
|
||||
}
|
||||
}
|
||||
|
||||
type StatsDTCPListener struct {
|
||||
Conn *net.TCPListener
|
||||
EventHandler event.EventHandler
|
||||
Logger log.Logger
|
||||
LineParser Parser
|
||||
LinesReceived prometheus.Counter
|
||||
EventsFlushed prometheus.Counter
|
||||
Relay *relay.Relay
|
||||
SampleErrors prometheus.CounterVec
|
||||
SamplesReceived prometheus.Counter
|
||||
TagErrors prometheus.Counter
|
||||
TagsReceived prometheus.Counter
|
||||
TCPConnections prometheus.Counter
|
||||
TCPErrors prometheus.Counter
|
||||
TCPLineTooLong prometheus.Counter
|
||||
}
|
||||
|
||||
func (l *StatsDTCPListener) SetEventHandler(eh event.EventHandler) {
|
||||
l.EventHandler = eh
|
||||
}
|
||||
|
||||
func (l *StatsDTCPListener) Listen() {
|
||||
for {
|
||||
c, err := l.Conn.AcceptTCP()
|
||||
if err != nil {
|
||||
// https://github.com/golang/go/issues/4373
|
||||
// ignore net: errClosing error as it will occur during shutdown
|
||||
if strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||
return
|
||||
}
|
||||
level.Error(l.Logger).Log("msg", "AcceptTCP failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
go l.HandleConn(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StatsDTCPListener) HandleConn(c *net.TCPConn) {
|
||||
defer c.Close()
|
||||
|
||||
l.TCPConnections.Inc()
|
||||
|
||||
r := bufio.NewReader(c)
|
||||
for {
|
||||
line, isPrefix, err := r.ReadLine()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
l.TCPErrors.Inc()
|
||||
level.Debug(l.Logger).Log("msg", "Read failed", "addr", c.RemoteAddr(), "error", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
level.Debug(l.Logger).Log("msg", "Incoming line", "proto", "tcp", "line", string(line))
|
||||
if isPrefix {
|
||||
l.TCPLineTooLong.Inc()
|
||||
level.Debug(l.Logger).Log("msg", "Read failed: line too long", "addr", c.RemoteAddr())
|
||||
break
|
||||
}
|
||||
l.LinesReceived.Inc()
|
||||
if l.Relay != nil && len(line) > 0 {
|
||||
l.Relay.RelayLine(string(line))
|
||||
}
|
||||
l.EventHandler.Queue(l.LineParser.LineToEvents(string(line), l.SampleErrors, l.SamplesReceived, l.TagErrors, l.TagsReceived, l.Logger))
|
||||
}
|
||||
}
|
||||
|
||||
type StatsDUnixgramListener struct {
|
||||
Conn *net.UnixConn
|
||||
EventHandler event.EventHandler
|
||||
Logger log.Logger
|
||||
LineParser Parser
|
||||
UnixgramPackets prometheus.Counter
|
||||
LinesReceived prometheus.Counter
|
||||
EventsFlushed prometheus.Counter
|
||||
Relay *relay.Relay
|
||||
SampleErrors prometheus.CounterVec
|
||||
SamplesReceived prometheus.Counter
|
||||
TagErrors prometheus.Counter
|
||||
TagsReceived prometheus.Counter
|
||||
}
|
||||
|
||||
func (l *StatsDUnixgramListener) SetEventHandler(eh event.EventHandler) {
|
||||
l.EventHandler = eh
|
||||
}
|
||||
|
||||
func (l *StatsDUnixgramListener) Listen() {
|
||||
buf := make([]byte, 65535)
|
||||
for {
|
||||
n, _, err := l.Conn.ReadFromUnix(buf)
|
||||
if err != nil {
|
||||
// https://github.com/golang/go/issues/4373
|
||||
// ignore net: errClosing error as it will occur during shutdown
|
||||
if strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||
return
|
||||
}
|
||||
level.Error(l.Logger).Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
l.HandlePacket(buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StatsDUnixgramListener) HandlePacket(packet []byte) {
|
||||
l.UnixgramPackets.Inc()
|
||||
lines := strings.Split(string(packet), "\n")
|
||||
for _, line := range lines {
|
||||
level.Debug(l.Logger).Log("msg", "Incoming line", "proto", "unixgram", "line", line)
|
||||
l.LinesReceived.Inc()
|
||||
if l.Relay != nil && len(line) > 0 {
|
||||
l.Relay.RelayLine(line)
|
||||
}
|
||||
l.EventHandler.Queue(l.LineParser.LineToEvents(line, l.SampleErrors, l.SamplesReceived, l.TagErrors, l.TagsReceived, l.Logger))
|
||||
}
|
||||
}
|
42
pkg/mapper/action.go
Normal file
42
pkg/mapper/action.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ActionType string
|
||||
|
||||
const (
|
||||
ActionTypeMap ActionType = "map"
|
||||
ActionTypeDrop ActionType = "drop"
|
||||
ActionTypeDefault ActionType = ""
|
||||
)
|
||||
|
||||
func (t *ActionType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var v string
|
||||
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch ActionType(v) {
|
||||
case ActionTypeDrop:
|
||||
*t = ActionTypeDrop
|
||||
case ActionTypeMap, ActionTypeDefault:
|
||||
*t = ActionTypeMap
|
||||
default:
|
||||
return fmt.Errorf("invalid action type %q", v)
|
||||
}
|
||||
return nil
|
||||
}
|
86
pkg/mapper/escape.go
Normal file
86
pkg/mapper/escape.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2020 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// EscapeMetricName replaces invalid characters in the metric name with "_"
|
||||
// Valid characters are a-z, A-Z, 0-9, and _
|
||||
func EscapeMetricName(metricName string) string {
|
||||
metricLen := len(metricName)
|
||||
if metricLen == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
escaped := false
|
||||
var sb strings.Builder
|
||||
// If a metric starts with a digit, allocate the memory and prepend an
|
||||
// underscore.
|
||||
if metricName[0] >= '0' && metricName[0] <= '9' {
|
||||
escaped = true
|
||||
sb.Grow(metricLen + 1)
|
||||
sb.WriteByte('_')
|
||||
}
|
||||
|
||||
// This is an character replacement method optimized for this limited
|
||||
// use case. It is much faster than using a regex.
|
||||
offset := 0
|
||||
|
||||
var prevChar rune
|
||||
|
||||
for i, c := range metricName {
|
||||
// Seek forward, skipping valid characters until we find one that needs
|
||||
// to be replaced, then add all the characters we've seen so far to the
|
||||
// string.Builder.
|
||||
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || (c == '_') {
|
||||
// Character is valid, so skip over it without doing anything.
|
||||
} else {
|
||||
// Double-dashes are allowed if there is a corresponding mapping.
|
||||
// For consistency, double-dashes should also be allowed in the default case.
|
||||
if c == '-' && prevChar == '-' {
|
||||
offset = i + utf8.RuneLen(c)
|
||||
continue
|
||||
}
|
||||
|
||||
if !escaped {
|
||||
// Up until now we've been lazy and avoided actually allocating
|
||||
// memory. Unfortunately we've now determined this string needs
|
||||
// escaping, so allocate the buffer for the whole string.
|
||||
escaped = true
|
||||
sb.Grow(metricLen)
|
||||
}
|
||||
sb.WriteString(metricName[offset:i])
|
||||
offset = i + utf8.RuneLen(c)
|
||||
sb.WriteByte('_')
|
||||
}
|
||||
|
||||
prevChar = c
|
||||
}
|
||||
|
||||
if !escaped {
|
||||
// This is the happy path where nothing had to be escaped, so we can
|
||||
// avoid doing anything.
|
||||
return metricName
|
||||
}
|
||||
|
||||
if offset < metricLen {
|
||||
sb.WriteString(metricName[offset:])
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
60
pkg/mapper/escape_test.go
Normal file
60
pkg/mapper/escape_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2020 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEscapeMetricName(t *testing.T) {
|
||||
scenarios := map[string]string{
|
||||
"clean": "clean",
|
||||
"0starts_with_digit": "_0starts_with_digit",
|
||||
"with_underscore": "with_underscore",
|
||||
"with--doubledash": "with_doubledash",
|
||||
"with---multiple-dashes": "with_multiple_dashes",
|
||||
"with.dot": "with_dot",
|
||||
"with😱emoji": "with_emoji",
|
||||
"with.*.multiple": "with___multiple",
|
||||
"test.web-server.foo.bar": "test_web_server_foo_bar",
|
||||
"": "",
|
||||
}
|
||||
|
||||
for in, want := range scenarios {
|
||||
if got := EscapeMetricName(in); want != got {
|
||||
t.Errorf("expected `%s` to be escaped to `%s`, got `%s`", in, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEscapeMetricName(b *testing.B) {
|
||||
scenarios := []string{
|
||||
"clean",
|
||||
"0starts_with_digit",
|
||||
"with_underscore",
|
||||
"with--doubledash",
|
||||
"with---multiple-dashes",
|
||||
"with.dot",
|
||||
"with😱emoji",
|
||||
"with.*.multiple",
|
||||
"test.web-server.foo.bar",
|
||||
"",
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
b.Run(s, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
EscapeMetricName(s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
132
pkg/mapper/fsm/README.md
Normal file
132
pkg/mapper/fsm/README.md
Normal file
|
@ -0,0 +1,132 @@
|
|||
# FSM Mapping
|
||||
|
||||
## Overview
|
||||
|
||||
This package implements a fast and efficient algorithm for generic glob style
|
||||
string matching using a finite state machine (FSM).
|
||||
|
||||
### Source Hierachy
|
||||
|
||||
```
|
||||
'-- fsm
|
||||
'-- dump.go // functionality to dump the FSM to Dot file
|
||||
'-- formatter.go // format glob templates using captured * groups
|
||||
'-- fsm.go // manipulating and searching of FSM
|
||||
'-- minmax.go // min() max() function for interger
|
||||
```
|
||||
|
||||
## FSM Explained
|
||||
|
||||
Per [Wikipedia](https://en.wikipedia.org/wiki/Finite-state_machine):
|
||||
|
||||
> A finite-state machine (FSM) or finite-state automaton (FSA, plural: automata),
|
||||
> finite automaton, or simply a state machine, is a mathematical model of
|
||||
> computation. It is an abstract machine that can be in exactly one of a finite
|
||||
> number of states at any given time. The FSM can change from one state to
|
||||
> another in response to some external inputs; the change from one state to
|
||||
> another is called a transition. An FSM is defined by a list of its states, its
|
||||
> initial state, and the conditions for each transition.
|
||||
|
||||
In our use case, each *state* is a substring after the input StatsD metric name is splitted by `.`.
|
||||
|
||||
### Add state to FSM
|
||||
|
||||
`func (f *FSM) AddState(match string, matchMetricType string,
|
||||
maxPossibleTransitions int, result interface{}) int`
|
||||
|
||||
At first, the FSM only contains three states, representing three possible metric types:
|
||||
|
||||
____ [gauge]
|
||||
/
|
||||
(start)---- [counter]
|
||||
\
|
||||
'--- [observer]
|
||||
|
||||
|
||||
Adding a rule `client.*.request.count` with type `counter` will make the FSM to be:
|
||||
|
||||
|
||||
____ [gauge]
|
||||
/
|
||||
(start)---- [counter] -- [client] -- [*] -- [request] -- [count] -- {R1}
|
||||
\
|
||||
'--- [observer]
|
||||
|
||||
`{R1}` is short for result 1, which is the match result for `client.*.request.count`.
|
||||
|
||||
Adding a rule `client.*.*.size` with type `counter` will make the FSM to be:
|
||||
|
||||
____ [gauge] __ [request] -- [count] -- {R1}
|
||||
/ /
|
||||
(start)---- [counter] -- [client] -- [*]
|
||||
\ \__ [*] -- [size] -- {R2}
|
||||
'--- [observer]
|
||||
|
||||
|
||||
### Finding a result state in FSM
|
||||
|
||||
`func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string)
|
||||
(*mappingState, []string)`
|
||||
|
||||
For example, when mapping `client.aaa.request.count` with `counter` type in the
|
||||
FSM, the `^1` to `^7` symbols indicate how FSM will traversal in its tree:
|
||||
|
||||
|
||||
____ [gauge] __ [request] -- [count] -- {R1}
|
||||
/ / ^5 ^6 ^7
|
||||
(start)---- [counter] -- [client] -- [*]
|
||||
^1 \ ^2 ^3 \__ [*] -- [size] -- {R2}
|
||||
'--- [observer] ^4
|
||||
|
||||
|
||||
To map `client.bbb.request.size`, FSM will do a backtracking:
|
||||
|
||||
|
||||
____ [gauge] __ [request] -- [count] -- {R1}
|
||||
/ / ^5 ^6
|
||||
(start)---- [counter] -- [client] -- [*]
|
||||
^1 \ ^2 ^3 \__ [*] -- [size] -- {R2}
|
||||
'--- [observer] ^4
|
||||
^7 ^8 ^9
|
||||
|
||||
|
||||
## Debugging
|
||||
|
||||
To see all the states of the current FSM, use `func (f *FSM) DumpFSM(w io.Writer)`
|
||||
to dump into a Dot file. The Dot file can be further renderer into image using:
|
||||
|
||||
```shell
|
||||
$ dot -Tpng dump.dot > dump.png
|
||||
```
|
||||
|
||||
In StatsD exporter, one could use the following:
|
||||
|
||||
```shell
|
||||
$ statsd_exporter --statsd.mapping-config=statsd.rules --debug.dump-fsm=dump.dot
|
||||
$ dot -Tpng dump.dot > dump.png
|
||||
```
|
||||
|
||||
For example, the following rules:
|
||||
|
||||
```yaml
|
||||
mappings:
|
||||
- match: client.*.request.count
|
||||
name: request_count
|
||||
match_metric_type: counter
|
||||
labels:
|
||||
client: $1
|
||||
|
||||
- match: client.*.*.size
|
||||
name: sizes
|
||||
match_metric_type: counter
|
||||
labels:
|
||||
client: $1
|
||||
direction: $2
|
||||
```
|
||||
|
||||
will be rendered as:
|
||||
|
||||
![FSM](fsm.png)
|
||||
|
||||
The `dot` program is part of [Graphviz](https://www.graphviz.org/) and is
|
||||
available in most of popular operating systems.
|
48
pkg/mapper/fsm/dump.go
Normal file
48
pkg/mapper/fsm/dump.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fsm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// DumpFSM accepts a io.writer and write the current FSM into dot file format.
|
||||
func (f *FSM) DumpFSM(w io.Writer) {
|
||||
idx := 0
|
||||
states := make(map[int]*mappingState)
|
||||
states[idx] = f.root
|
||||
|
||||
w.Write([]byte("digraph g {\n"))
|
||||
w.Write([]byte("rankdir=LR\n")) // make it vertical
|
||||
w.Write([]byte("node [ label=\"\",style=filled,fillcolor=white,shape=circle ]\n")) // remove label of node
|
||||
|
||||
for idx < len(states) {
|
||||
for field, transition := range states[idx].transitions {
|
||||
states[len(states)] = transition
|
||||
w.Write([]byte(fmt.Sprintf("%d -> %d [label = \"%s\"];\n", idx, len(states)-1, field)))
|
||||
if idx == 0 {
|
||||
// color for metric types
|
||||
w.Write([]byte(fmt.Sprintf("%d [color=\"#D6B656\",fillcolor=\"#FFF2CC\"];\n", len(states)-1)))
|
||||
} else if len(transition.transitions) == 0 {
|
||||
// color for end state
|
||||
w.Write([]byte(fmt.Sprintf("%d [color=\"#82B366\",fillcolor=\"#D5E8D4\"];\n", len(states)-1)))
|
||||
}
|
||||
}
|
||||
idx++
|
||||
}
|
||||
// color for start state
|
||||
w.Write([]byte(fmt.Sprintln("0 [color=\"#a94442\",fillcolor=\"#f2dede\"];")))
|
||||
w.Write([]byte("}"))
|
||||
}
|
76
pkg/mapper/fsm/formatter.go
Normal file
76
pkg/mapper/fsm/formatter.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fsm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`)
|
||||
)
|
||||
|
||||
type TemplateFormatter struct {
|
||||
captureIndexes []int
|
||||
captureCount int
|
||||
fmtString string
|
||||
}
|
||||
|
||||
// NewTemplateFormatter instantiates a TemplateFormatter
|
||||
// from given template string and the maximum amount of captures.
|
||||
func NewTemplateFormatter(template string, captureCount int) *TemplateFormatter {
|
||||
matches := templateReplaceCaptureRE.FindAllStringSubmatch(template, -1)
|
||||
if len(matches) == 0 {
|
||||
// if no regex reference found, keep it as it is
|
||||
return &TemplateFormatter{captureCount: 0, fmtString: template}
|
||||
}
|
||||
|
||||
var indexes []int
|
||||
valueFormatter := template
|
||||
for _, match := range matches {
|
||||
idx, err := strconv.Atoi(match[len(match)-1])
|
||||
if err != nil || idx > captureCount || idx < 1 {
|
||||
// if index larger than captured count or using unsupported named capture group,
|
||||
// replace with empty string
|
||||
valueFormatter = strings.Replace(valueFormatter, match[0], "", -1)
|
||||
} else {
|
||||
valueFormatter = strings.Replace(valueFormatter, match[0], "%s", -1)
|
||||
// note: the regex reference variable $? starts from 1
|
||||
indexes = append(indexes, idx-1)
|
||||
}
|
||||
}
|
||||
return &TemplateFormatter{
|
||||
captureIndexes: indexes,
|
||||
captureCount: len(indexes),
|
||||
fmtString: valueFormatter,
|
||||
}
|
||||
}
|
||||
|
||||
// Format accepts a list containing captured strings and returns the formatted
|
||||
// string using the template stored in current TemplateFormatter.
|
||||
func (formatter *TemplateFormatter) Format(captures []string) string {
|
||||
if formatter.captureCount == 0 {
|
||||
// no label substitution, keep as it is
|
||||
return formatter.fmtString
|
||||
}
|
||||
indexes := formatter.captureIndexes
|
||||
vargs := make([]interface{}, formatter.captureCount)
|
||||
for i, idx := range indexes {
|
||||
vargs[i] = captures[idx]
|
||||
}
|
||||
return fmt.Sprintf(formatter.fmtString, vargs...)
|
||||
}
|
326
pkg/mapper/fsm/fsm.go
Normal file
326
pkg/mapper/fsm/fsm.go
Normal file
|
@ -0,0 +1,326 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fsm
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/level"
|
||||
)
|
||||
|
||||
type mappingState struct {
|
||||
transitions map[string]*mappingState
|
||||
minRemainingLength int
|
||||
maxRemainingLength int
|
||||
// result* members are nil unless there's a metric ends with this state
|
||||
Result interface{}
|
||||
ResultPriority int
|
||||
}
|
||||
|
||||
type fsmBacktrackStackCursor struct {
|
||||
fieldIndex int
|
||||
captureIndex int
|
||||
currentCapture string
|
||||
state *mappingState
|
||||
prev *fsmBacktrackStackCursor
|
||||
next *fsmBacktrackStackCursor
|
||||
}
|
||||
|
||||
type FSM struct {
|
||||
root *mappingState
|
||||
metricTypes []string
|
||||
statesCount int
|
||||
BacktrackingNeeded bool
|
||||
OrderingDisabled bool
|
||||
}
|
||||
|
||||
// NewFSM creates a new FSM instance
|
||||
func NewFSM(metricTypes []string, maxPossibleTransitions int, orderingDisabled bool) *FSM {
|
||||
fsm := FSM{}
|
||||
root := &mappingState{}
|
||||
root.transitions = make(map[string]*mappingState, len(metricTypes))
|
||||
|
||||
for _, field := range metricTypes {
|
||||
state := &mappingState{}
|
||||
(*state).transitions = make(map[string]*mappingState, maxPossibleTransitions)
|
||||
root.transitions[string(field)] = state
|
||||
}
|
||||
fsm.OrderingDisabled = orderingDisabled
|
||||
fsm.metricTypes = metricTypes
|
||||
fsm.statesCount = 0
|
||||
fsm.root = root
|
||||
return &fsm
|
||||
}
|
||||
|
||||
// AddState adds a mapping rule into the existing FSM.
|
||||
// The maxPossibleTransitions parameter sets the expected count of transitions left.
|
||||
// The result parameter sets the generic type to be returned when fsm found a match in GetMapping.
|
||||
func (f *FSM) AddState(match string, matchMetricType string, maxPossibleTransitions int, result interface{}) int {
|
||||
// first split by "."
|
||||
matchFields := strings.Split(match, ".")
|
||||
// fill into our FSM
|
||||
roots := []*mappingState{}
|
||||
// first state is the metric type
|
||||
if matchMetricType == "" {
|
||||
// if metricType not specified, connect the start state from all three types
|
||||
for _, metricType := range f.metricTypes {
|
||||
roots = append(roots, f.root.transitions[string(metricType)])
|
||||
}
|
||||
} else {
|
||||
roots = append(roots, f.root.transitions[matchMetricType])
|
||||
}
|
||||
var captureCount int
|
||||
var finalStates []*mappingState
|
||||
// iterating over different start state (different metric types)
|
||||
for _, root := range roots {
|
||||
captureCount = 0
|
||||
// for each start state, connect from start state to end state
|
||||
for i, field := range matchFields {
|
||||
state, prs := root.transitions[field]
|
||||
if !prs {
|
||||
// create a state if it's not exist in the fsm
|
||||
state = &mappingState{}
|
||||
(*state).transitions = make(map[string]*mappingState, maxPossibleTransitions)
|
||||
(*state).maxRemainingLength = len(matchFields) - i - 1
|
||||
(*state).minRemainingLength = len(matchFields) - i - 1
|
||||
root.transitions[field] = state
|
||||
// if this is last field, set result to currentMapping instance
|
||||
if i == len(matchFields)-1 {
|
||||
root.transitions[field].Result = result
|
||||
}
|
||||
} else {
|
||||
(*state).maxRemainingLength = max(len(matchFields)-i-1, (*state).maxRemainingLength)
|
||||
(*state).minRemainingLength = min(len(matchFields)-i-1, (*state).minRemainingLength)
|
||||
}
|
||||
if field == "*" {
|
||||
captureCount++
|
||||
}
|
||||
|
||||
// goto next state
|
||||
root = state
|
||||
}
|
||||
finalStates = append(finalStates, root)
|
||||
}
|
||||
|
||||
for _, state := range finalStates {
|
||||
state.ResultPriority = f.statesCount
|
||||
}
|
||||
|
||||
f.statesCount++
|
||||
|
||||
return captureCount
|
||||
}
|
||||
|
||||
// GetMapping using the fsm to find matching rules according to given statsdMetric and statsdMetricType.
|
||||
// If it finds a match, the final state and the captured strings are returned;
|
||||
// if there's no match found, nil and a empty list will be returned.
|
||||
func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) (*mappingState, []string) {
|
||||
matchFields := strings.Split(statsdMetric, ".")
|
||||
currentState := f.root.transitions[statsdMetricType]
|
||||
|
||||
// the cursor/pointer in the backtrack stack implemented as a double-linked list
|
||||
var backtrackCursor *fsmBacktrackStackCursor
|
||||
resumeFromBacktrack := false
|
||||
|
||||
// the return variable
|
||||
var finalState *mappingState
|
||||
|
||||
captures := make([]string, len(matchFields))
|
||||
finalCaptures := make([]string, len(matchFields))
|
||||
// keep track of captured group so we don't need to do append() on captures
|
||||
captureIdx := 0
|
||||
filedsCount := len(matchFields)
|
||||
i := 0
|
||||
var state *mappingState
|
||||
for { // the loop for backtracking
|
||||
for { // the loop for a single "depth only" search
|
||||
var present bool
|
||||
// if we resume from backtrack, we should skip this branch in this case
|
||||
// since the state that were saved at the end of this branch
|
||||
if !resumeFromBacktrack {
|
||||
if len(currentState.transitions) > 0 {
|
||||
field := matchFields[i]
|
||||
state, present = currentState.transitions[field]
|
||||
fieldsLeft := filedsCount - i - 1
|
||||
// also compare length upfront to avoid unnecessary loop or backtrack
|
||||
if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength {
|
||||
state, present = currentState.transitions["*"]
|
||||
if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength {
|
||||
break
|
||||
} else {
|
||||
captures[captureIdx] = field
|
||||
captureIdx++
|
||||
}
|
||||
} else if f.BacktrackingNeeded {
|
||||
// if backtracking is needed, also check for alternative transition, i.e. *
|
||||
altState, present := currentState.transitions["*"]
|
||||
if !present || fieldsLeft > altState.maxRemainingLength || fieldsLeft < altState.minRemainingLength {
|
||||
} else {
|
||||
// push to backtracking stack
|
||||
newCursor := fsmBacktrackStackCursor{prev: backtrackCursor, state: altState,
|
||||
fieldIndex: i,
|
||||
captureIndex: captureIdx, currentCapture: field,
|
||||
}
|
||||
// if this is not the first time, connect to the previous cursor
|
||||
if backtrackCursor != nil {
|
||||
backtrackCursor.next = &newCursor
|
||||
}
|
||||
backtrackCursor = &newCursor
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no more transitions for this state
|
||||
break
|
||||
}
|
||||
} // backtrack will resume from here
|
||||
|
||||
// do we reach a final state?
|
||||
if state.Result != nil && i == filedsCount-1 {
|
||||
if f.OrderingDisabled {
|
||||
finalState = state
|
||||
return finalState, captures
|
||||
} else if finalState == nil || finalState.ResultPriority > state.ResultPriority {
|
||||
// if we care about ordering, try to find a result with highest prioity
|
||||
finalState = state
|
||||
// do a deep copy to preserve current captures
|
||||
copy(finalCaptures, captures)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
i++
|
||||
if i >= filedsCount {
|
||||
break
|
||||
}
|
||||
|
||||
resumeFromBacktrack = false
|
||||
currentState = state
|
||||
}
|
||||
if backtrackCursor == nil {
|
||||
// if we are not doing backtracking or all path has been travesaled
|
||||
break
|
||||
} else {
|
||||
// pop one from stack
|
||||
state = backtrackCursor.state
|
||||
currentState = state
|
||||
i = backtrackCursor.fieldIndex
|
||||
captureIdx = backtrackCursor.captureIndex + 1
|
||||
// put the * capture back
|
||||
captures[captureIdx-1] = backtrackCursor.currentCapture
|
||||
backtrackCursor = backtrackCursor.prev
|
||||
if backtrackCursor != nil {
|
||||
// deref for GC
|
||||
backtrackCursor.next = nil
|
||||
}
|
||||
resumeFromBacktrack = true
|
||||
}
|
||||
}
|
||||
return finalState, finalCaptures
|
||||
}
|
||||
|
||||
// TestIfNeedBacktracking tests if backtrack is needed for given list of mappings
|
||||
// and whether ordering is disabled.
|
||||
func TestIfNeedBacktracking(mappings []string, orderingDisabled bool, logger log.Logger) bool {
|
||||
backtrackingNeeded := false
|
||||
// A has * in rules, but there's other transisitions at the same state,
|
||||
// this makes A the cause of backtracking
|
||||
ruleByLength := make(map[int][]string)
|
||||
ruleREByLength := make(map[int][]*regexp.Regexp)
|
||||
|
||||
// first sort rules by length
|
||||
for _, mapping := range mappings {
|
||||
l := len(strings.Split(mapping, "."))
|
||||
ruleByLength[l] = append(ruleByLength[l], mapping)
|
||||
|
||||
metricRe := strings.Replace(mapping, ".", "\\.", -1)
|
||||
metricRe = strings.Replace(metricRe, "*", "([^.]*)", -1)
|
||||
regex, err := regexp.Compile("^" + metricRe + "$")
|
||||
if err != nil {
|
||||
level.Warn(logger).Log("msg", "Invalid match, cannot compile regex in mapping", "mapping", mapping, "err", err)
|
||||
}
|
||||
// put into array no matter there's error or not, we will skip later if regex is nil
|
||||
ruleREByLength[l] = append(ruleREByLength[l], regex)
|
||||
}
|
||||
|
||||
for l, rules := range ruleByLength {
|
||||
if len(rules) == 1 {
|
||||
continue
|
||||
}
|
||||
rulesRE := ruleREByLength[l]
|
||||
for i1, r1 := range rules {
|
||||
currentRuleNeedBacktrack := false
|
||||
re1 := rulesRE[i1]
|
||||
if re1 == nil || !strings.Contains(r1, "*") {
|
||||
continue
|
||||
}
|
||||
// if rule r1 is A.B.C.*.E.*, is there a rule r2 is A.B.C.D.x.x or A.B.C.*.E.F ? (x is any string or *)
|
||||
// if such r2 exists, then to match r1 we will need backtracking
|
||||
for index := 0; index < len(r1); index++ {
|
||||
if r1[index] != '*' {
|
||||
continue
|
||||
}
|
||||
// translate the substring of r1 from 0 to the index of current * into regex
|
||||
// A.B.C.*.E.* will becomes ^A\.B\.C\. and ^A\.B\.C\.\*\.E\.
|
||||
reStr := strings.Replace(r1[:index], ".", "\\.", -1)
|
||||
reStr = strings.Replace(reStr, "*", "\\*", -1)
|
||||
re := regexp.MustCompile("^" + reStr)
|
||||
for i2, r2 := range rules {
|
||||
if i2 == i1 {
|
||||
continue
|
||||
}
|
||||
if len(re.FindStringSubmatchIndex(r2)) > 0 {
|
||||
currentRuleNeedBacktrack = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i2, r2 := range rules {
|
||||
if i2 != i1 && len(re1.FindStringSubmatchIndex(r2)) > 0 {
|
||||
// log if we care about ordering and the superset occurs before
|
||||
if !orderingDisabled && i1 < i2 {
|
||||
level.Warn(logger).Log("msg", "match is a super set of match but in a lower order, the first will never be matched", "first_match", r1, "second_match", r2)
|
||||
}
|
||||
currentRuleNeedBacktrack = false
|
||||
}
|
||||
}
|
||||
for i2, re2 := range rulesRE {
|
||||
if i2 == i1 || re2 == nil {
|
||||
continue
|
||||
}
|
||||
// if r1 is a subset of other rule, we don't need backtrack
|
||||
// because either we turned on ordering
|
||||
// or we disabled ordering and can't match it even with backtrack
|
||||
if len(re2.FindStringSubmatchIndex(r1)) > 0 {
|
||||
currentRuleNeedBacktrack = false
|
||||
}
|
||||
}
|
||||
|
||||
if currentRuleNeedBacktrack {
|
||||
level.Warn(logger).Log("msg", "backtracking required because of match. Performance may be degraded", "match", r1)
|
||||
backtrackingNeeded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// backtracking will always be needed if ordering of rules is not disabled
|
||||
// since transistions are stored in (unordered) map
|
||||
// note: don't move this branch to the beginning of this function
|
||||
// since we need logs for superset rules
|
||||
|
||||
return !orderingDisabled || backtrackingNeeded
|
||||
}
|
BIN
pkg/mapper/fsm/fsm.png
Normal file
BIN
pkg/mapper/fsm/fsm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
@ -11,6 +11,20 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package model contains common data structures that are shared across
|
||||
// Prometheus componenets and libraries.
|
||||
package model
|
||||
package fsm
|
||||
|
||||
// min and max implementation for integer
|
||||
|
||||
func min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func max(x, y int) int {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
394
pkg/mapper/mapper.go
Normal file
394
pkg/mapper/mapper.go
Normal file
|
@ -0,0 +1,394 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/level"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper/fsm"
|
||||
)
|
||||
|
||||
var (
|
||||
// The first segment of a match cannot start with a number
|
||||
statsdMetricRE = `[a-zA-Z_]([a-zA-Z0-9_\-])*`
|
||||
// The subsequent segments of a match can start with a number
|
||||
// See https://github.com/prometheus/statsd_exporter/issues/328
|
||||
statsdMetricSubsequentRE = `[a-zA-Z0-9_]([a-zA-Z0-9_\-])*`
|
||||
templateReplaceRE = `(\$\{?\d+\}?)`
|
||||
|
||||
metricLineRE = regexp.MustCompile(`^(\*|` + statsdMetricRE + `)(\.\*|\.` + statsdMetricSubsequentRE + `)*$`)
|
||||
metricNameRE = regexp.MustCompile(`^([a-zA-Z_]|` + templateReplaceRE + `)([a-zA-Z0-9_]|` + templateReplaceRE + `)*$`)
|
||||
labelNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]+$`)
|
||||
)
|
||||
|
||||
type MetricMapper struct {
|
||||
Registerer prometheus.Registerer
|
||||
Defaults MapperConfigDefaults `yaml:"defaults"`
|
||||
Mappings []MetricMapping `yaml:"mappings"`
|
||||
FSM *fsm.FSM
|
||||
doFSM bool
|
||||
doRegex bool
|
||||
cache MetricMapperCache
|
||||
mutex sync.RWMutex
|
||||
|
||||
MappingsCount prometheus.Gauge
|
||||
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
type SummaryOptions struct {
|
||||
Quantiles []MetricObjective `yaml:"quantiles"`
|
||||
MaxAge time.Duration `yaml:"max_age"`
|
||||
AgeBuckets uint32 `yaml:"age_buckets"`
|
||||
BufCap uint32 `yaml:"buf_cap"`
|
||||
}
|
||||
|
||||
type HistogramOptions struct {
|
||||
Buckets []float64 `yaml:"buckets"`
|
||||
NativeHistogramBucketFactor float64 `yaml:"native_histogram_bucket_factor"`
|
||||
NativeHistogramMaxBuckets uint32 `yaml:"native_histogram_max_buckets"`
|
||||
}
|
||||
|
||||
type MetricObjective struct {
|
||||
Quantile float64 `yaml:"quantile"`
|
||||
Error float64 `yaml:"error"`
|
||||
}
|
||||
|
||||
var defaultQuantiles = []MetricObjective{
|
||||
{Quantile: 0.5, Error: 0.05},
|
||||
{Quantile: 0.9, Error: 0.01},
|
||||
{Quantile: 0.99, Error: 0.001},
|
||||
}
|
||||
|
||||
func (m *MetricMapper) InitFromYAMLString(fileContents string) error {
|
||||
var n MetricMapper
|
||||
|
||||
if err := yaml.Unmarshal([]byte(fileContents), &n); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(n.Defaults.HistogramOptions.Buckets) == 0 {
|
||||
n.Defaults.HistogramOptions.Buckets = prometheus.DefBuckets
|
||||
}
|
||||
if n.Defaults.HistogramOptions.NativeHistogramBucketFactor == 0 {
|
||||
n.Defaults.HistogramOptions.NativeHistogramBucketFactor = 1.1
|
||||
}
|
||||
if n.Defaults.HistogramOptions.NativeHistogramMaxBuckets <= 0 {
|
||||
n.Defaults.HistogramOptions.NativeHistogramMaxBuckets = 256
|
||||
}
|
||||
|
||||
if len(n.Defaults.SummaryOptions.Quantiles) == 0 {
|
||||
n.Defaults.SummaryOptions.Quantiles = defaultQuantiles
|
||||
}
|
||||
|
||||
if n.Defaults.MatchType == MatchTypeDefault {
|
||||
n.Defaults.MatchType = MatchTypeGlob
|
||||
}
|
||||
|
||||
remainingMappingsCount := len(n.Mappings)
|
||||
|
||||
n.FSM = fsm.NewFSM([]string{string(MetricTypeCounter), string(MetricTypeGauge), string(MetricTypeObserver)},
|
||||
remainingMappingsCount, n.Defaults.GlobDisableOrdering)
|
||||
|
||||
for i := range n.Mappings {
|
||||
remainingMappingsCount--
|
||||
|
||||
currentMapping := &n.Mappings[i]
|
||||
|
||||
// check that label is correct
|
||||
for k := range currentMapping.Labels {
|
||||
if !labelNameRE.MatchString(k) {
|
||||
return fmt.Errorf("invalid label key: %s", k)
|
||||
}
|
||||
}
|
||||
|
||||
if currentMapping.Name == "" {
|
||||
return fmt.Errorf("line %d: metric mapping didn't set a metric name", i)
|
||||
}
|
||||
|
||||
if !metricNameRE.MatchString(currentMapping.Name) {
|
||||
return fmt.Errorf("metric name '%s' doesn't match regex '%s'", currentMapping.Name, metricNameRE)
|
||||
}
|
||||
|
||||
if currentMapping.MatchType == "" {
|
||||
currentMapping.MatchType = n.Defaults.MatchType
|
||||
}
|
||||
|
||||
if currentMapping.Action == "" {
|
||||
currentMapping.Action = ActionTypeMap
|
||||
}
|
||||
|
||||
if currentMapping.MatchType == MatchTypeGlob {
|
||||
n.doFSM = true
|
||||
if !metricLineRE.MatchString(currentMapping.Match) {
|
||||
return fmt.Errorf("invalid match: %s", currentMapping.Match)
|
||||
}
|
||||
|
||||
captureCount := n.FSM.AddState(currentMapping.Match, string(currentMapping.MatchMetricType),
|
||||
remainingMappingsCount, currentMapping)
|
||||
|
||||
currentMapping.nameFormatter = fsm.NewTemplateFormatter(currentMapping.Name, captureCount)
|
||||
|
||||
labelKeys := make([]string, len(currentMapping.Labels))
|
||||
labelFormatters := make([]*fsm.TemplateFormatter, len(currentMapping.Labels))
|
||||
labelIndex := 0
|
||||
for label, valueExpr := range currentMapping.Labels {
|
||||
labelKeys[labelIndex] = label
|
||||
labelFormatters[labelIndex] = fsm.NewTemplateFormatter(valueExpr, captureCount)
|
||||
labelIndex++
|
||||
}
|
||||
currentMapping.labelFormatters = labelFormatters
|
||||
currentMapping.labelKeys = labelKeys
|
||||
} else {
|
||||
if regex, err := regexp.Compile(currentMapping.Match); err != nil {
|
||||
return fmt.Errorf("invalid regex %s in mapping: %v", currentMapping.Match, err)
|
||||
} else {
|
||||
currentMapping.regex = regex
|
||||
}
|
||||
n.doRegex = true
|
||||
}
|
||||
|
||||
if currentMapping.ObserverType == "" {
|
||||
currentMapping.ObserverType = n.Defaults.ObserverType
|
||||
}
|
||||
|
||||
if currentMapping.LegacyQuantiles != nil &&
|
||||
(currentMapping.SummaryOptions == nil || currentMapping.SummaryOptions.Quantiles != nil) {
|
||||
level.Warn(m.Logger).Log("msg", "using the top level quantiles is deprecated. Please use quantiles in the summary_options hierarchy")
|
||||
}
|
||||
|
||||
if currentMapping.LegacyBuckets != nil &&
|
||||
(currentMapping.HistogramOptions == nil || currentMapping.HistogramOptions.Buckets != nil) {
|
||||
level.Warn(m.Logger).Log("msg", "using the top level buckets is deprecated. Please use buckets in the histogram_options hierarchy")
|
||||
}
|
||||
|
||||
if currentMapping.SummaryOptions != nil &&
|
||||
currentMapping.LegacyQuantiles != nil &&
|
||||
currentMapping.SummaryOptions.Quantiles != nil {
|
||||
return fmt.Errorf("cannot use quantiles in both the top level and summary options at the same time in %s", currentMapping.Match)
|
||||
}
|
||||
|
||||
if currentMapping.HistogramOptions != nil &&
|
||||
currentMapping.LegacyBuckets != nil &&
|
||||
currentMapping.HistogramOptions.Buckets != nil {
|
||||
return fmt.Errorf("cannot use buckets in both the top level and histogram options at the same time in %s", currentMapping.Match)
|
||||
}
|
||||
|
||||
if currentMapping.ObserverType == ObserverTypeHistogram {
|
||||
if currentMapping.SummaryOptions != nil {
|
||||
return fmt.Errorf("cannot use histogram observer and summary options at the same time")
|
||||
}
|
||||
if currentMapping.HistogramOptions == nil {
|
||||
currentMapping.HistogramOptions = &HistogramOptions{}
|
||||
}
|
||||
if len(currentMapping.LegacyBuckets) != 0 {
|
||||
currentMapping.HistogramOptions.Buckets = currentMapping.LegacyBuckets
|
||||
}
|
||||
if len(currentMapping.HistogramOptions.Buckets) == 0 {
|
||||
currentMapping.HistogramOptions.Buckets = n.Defaults.HistogramOptions.Buckets
|
||||
}
|
||||
}
|
||||
|
||||
if currentMapping.ObserverType == ObserverTypeSummary {
|
||||
if currentMapping.HistogramOptions != nil {
|
||||
return fmt.Errorf("cannot use summary observer and histogram options at the same time")
|
||||
}
|
||||
if currentMapping.SummaryOptions == nil {
|
||||
currentMapping.SummaryOptions = &SummaryOptions{}
|
||||
}
|
||||
if len(currentMapping.LegacyQuantiles) != 0 {
|
||||
currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles
|
||||
}
|
||||
if len(currentMapping.SummaryOptions.Quantiles) == 0 {
|
||||
currentMapping.SummaryOptions.Quantiles = n.Defaults.SummaryOptions.Quantiles
|
||||
}
|
||||
if currentMapping.SummaryOptions.MaxAge == 0 {
|
||||
currentMapping.SummaryOptions.MaxAge = n.Defaults.SummaryOptions.MaxAge
|
||||
}
|
||||
if currentMapping.SummaryOptions.AgeBuckets == 0 {
|
||||
currentMapping.SummaryOptions.AgeBuckets = n.Defaults.SummaryOptions.AgeBuckets
|
||||
}
|
||||
if currentMapping.SummaryOptions.BufCap == 0 {
|
||||
currentMapping.SummaryOptions.BufCap = n.Defaults.SummaryOptions.BufCap
|
||||
}
|
||||
}
|
||||
|
||||
if currentMapping.Ttl == 0 && n.Defaults.Ttl > 0 {
|
||||
currentMapping.Ttl = n.Defaults.Ttl
|
||||
}
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if m.Logger == nil {
|
||||
m.Logger = log.NewNopLogger()
|
||||
}
|
||||
|
||||
m.Defaults = n.Defaults
|
||||
m.Mappings = n.Mappings
|
||||
|
||||
// Reset the cache since this function can be used to reload config
|
||||
if m.cache != nil {
|
||||
m.cache.Reset()
|
||||
}
|
||||
|
||||
if n.doFSM {
|
||||
var mappings []string
|
||||
for _, mapping := range n.Mappings {
|
||||
if mapping.MatchType == MatchTypeGlob {
|
||||
mappings = append(mappings, mapping.Match)
|
||||
}
|
||||
}
|
||||
n.FSM.BacktrackingNeeded = fsm.TestIfNeedBacktracking(mappings, n.FSM.OrderingDisabled, m.Logger)
|
||||
|
||||
m.FSM = n.FSM
|
||||
m.doRegex = n.doRegex
|
||||
}
|
||||
m.doFSM = n.doFSM
|
||||
|
||||
if m.MappingsCount != nil {
|
||||
m.MappingsCount.Set(float64(len(n.Mappings)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MetricMapper) InitFromFile(fileName string) error {
|
||||
mappingStr, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.InitFromYAMLString(string(mappingStr))
|
||||
}
|
||||
|
||||
// UseCache tells the mapper to use a cache that implements the MetricMapperCache interface.
|
||||
// This cache MUST be thread-safe!
|
||||
func (m *MetricMapper) UseCache(cache MetricMapperCache) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.cache = cache
|
||||
}
|
||||
|
||||
func (m *MetricMapper) GetMapping(statsdMetric string, statsdMetricType MetricType) (*MetricMapping, prometheus.Labels, bool) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
// only use a cache if one is present
|
||||
if m.cache != nil {
|
||||
result, cached := m.cache.Get(formatKey(statsdMetric, statsdMetricType))
|
||||
if cached {
|
||||
r := result.(MetricMapperCacheResult)
|
||||
return r.Mapping, r.Labels, r.Matched
|
||||
}
|
||||
}
|
||||
|
||||
// glob matching
|
||||
if m.doFSM {
|
||||
finalState, captures := m.FSM.GetMapping(statsdMetric, string(statsdMetricType))
|
||||
if finalState != nil && finalState.Result != nil {
|
||||
v := finalState.Result.(*MetricMapping)
|
||||
result := copyMetricMapping(v)
|
||||
result.Name = result.nameFormatter.Format(captures)
|
||||
|
||||
labels := prometheus.Labels{}
|
||||
for index, formatter := range result.labelFormatters {
|
||||
labels[result.labelKeys[index]] = formatter.Format(captures)
|
||||
}
|
||||
|
||||
r := MetricMapperCacheResult{
|
||||
Mapping: result,
|
||||
Matched: true,
|
||||
Labels: labels,
|
||||
}
|
||||
// add match to cache
|
||||
if m.cache != nil {
|
||||
m.cache.Add(formatKey(statsdMetric, statsdMetricType), r)
|
||||
}
|
||||
|
||||
return result, labels, true
|
||||
} else if !m.doRegex {
|
||||
// if there's no regex match type, return immediately
|
||||
// Add miss to cache
|
||||
if m.cache != nil {
|
||||
m.cache.Add(formatKey(statsdMetric, statsdMetricType), MetricMapperCacheResult{})
|
||||
}
|
||||
return nil, nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// regex matching
|
||||
for _, mapping := range m.Mappings {
|
||||
// if a rule don't have regex matching type, the regex field is unset
|
||||
if mapping.regex == nil {
|
||||
continue
|
||||
}
|
||||
matches := mapping.regex.FindStringSubmatchIndex(statsdMetric)
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
mapping.Name = string(mapping.regex.ExpandString(
|
||||
[]byte{},
|
||||
mapping.Name,
|
||||
statsdMetric,
|
||||
matches,
|
||||
))
|
||||
|
||||
if mt := mapping.MatchMetricType; mt != "" && mt != statsdMetricType {
|
||||
continue
|
||||
}
|
||||
|
||||
labels := prometheus.Labels{}
|
||||
for label, valueExpr := range mapping.Labels {
|
||||
value := mapping.regex.ExpandString([]byte{}, valueExpr, statsdMetric, matches)
|
||||
labels[label] = string(value)
|
||||
}
|
||||
|
||||
r := MetricMapperCacheResult{
|
||||
Mapping: &mapping,
|
||||
Matched: true,
|
||||
Labels: labels,
|
||||
}
|
||||
// Add Match to cache
|
||||
if m.cache != nil {
|
||||
m.cache.Add(formatKey(statsdMetric, statsdMetricType), r)
|
||||
}
|
||||
|
||||
return &mapping, labels, true
|
||||
}
|
||||
|
||||
// Add Miss to cache
|
||||
if m.cache != nil {
|
||||
m.cache.Add(formatKey(statsdMetric, statsdMetricType), MetricMapperCacheResult{})
|
||||
}
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// make a shallow copy so that we do not overwrite name
|
||||
// as multiple names can be matched by same mapping
|
||||
func copyMetricMapping(in *MetricMapping) *MetricMapping {
|
||||
out := *in
|
||||
return &out
|
||||
}
|
1023
pkg/mapper/mapper_benchmark_test.go
Normal file
1023
pkg/mapper/mapper_benchmark_test.go
Normal file
File diff suppressed because it is too large
Load diff
74
pkg/mapper/mapper_cache.go
Normal file
74
pkg/mapper/mapper_cache.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type CacheMetrics struct {
|
||||
CacheLength prometheus.Gauge
|
||||
CacheGetsTotal prometheus.Counter
|
||||
CacheHitsTotal prometheus.Counter
|
||||
}
|
||||
|
||||
func NewCacheMetrics(reg prometheus.Registerer) *CacheMetrics {
|
||||
var m CacheMetrics
|
||||
|
||||
m.CacheLength = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "statsd_metric_mapper_cache_length",
|
||||
Help: "The count of unique metrics currently cached.",
|
||||
},
|
||||
)
|
||||
m.CacheGetsTotal = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_metric_mapper_cache_gets_total",
|
||||
Help: "The count of total metric cache gets.",
|
||||
},
|
||||
)
|
||||
m.CacheHitsTotal = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_metric_mapper_cache_hits_total",
|
||||
Help: "The count of total metric cache hits.",
|
||||
},
|
||||
)
|
||||
|
||||
if reg != nil {
|
||||
reg.MustRegister(m.CacheLength)
|
||||
reg.MustRegister(m.CacheGetsTotal)
|
||||
reg.MustRegister(m.CacheHitsTotal)
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
type MetricMapperCacheResult struct {
|
||||
Mapping *MetricMapping
|
||||
Matched bool
|
||||
Labels prometheus.Labels
|
||||
}
|
||||
|
||||
// MetricMapperCache MUST be thread-safe and should be instrumented with CacheMetrics
|
||||
type MetricMapperCache interface {
|
||||
// Get a cached result
|
||||
Get(metricKey string) (interface{}, bool)
|
||||
// Add a statsd MetricMapperResult to the cache
|
||||
Add(metricKey string, result interface{}) // Add an item to the cache
|
||||
// Reset clears the cache for config reloads
|
||||
Reset()
|
||||
}
|
||||
|
||||
func formatKey(metricString string, metricType MetricType) string {
|
||||
return string(metricType) + "." + metricString
|
||||
}
|
72
pkg/mapper/mapper_defaults.go
Normal file
72
pkg/mapper/mapper_defaults.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2020 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import "time"
|
||||
|
||||
type MapperConfigDefaults struct {
|
||||
ObserverType ObserverType `yaml:"observer_type"`
|
||||
MatchType MatchType `yaml:"match_type"`
|
||||
GlobDisableOrdering bool `yaml:"glob_disable_ordering"`
|
||||
Ttl time.Duration `yaml:"ttl"`
|
||||
SummaryOptions SummaryOptions `yaml:"summary_options"`
|
||||
HistogramOptions HistogramOptions `yaml:"histogram_options"`
|
||||
}
|
||||
|
||||
// mapperConfigDefaultsAlias is used to unmarshal the yaml config into mapperConfigDefaults and allows deprecated fields
|
||||
type mapperConfigDefaultsAlias struct {
|
||||
ObserverType ObserverType `yaml:"observer_type"`
|
||||
TimerType ObserverType `yaml:"timer_type,omitempty"` // DEPRECATED - field only present to preserve backwards compatibility in configs
|
||||
Buckets []float64 `yaml:"buckets"` // DEPRECATED - field only present to preserve backwards compatibility in configs
|
||||
Quantiles []MetricObjective `yaml:"quantiles"` // DEPRECATED - field only present to preserve backwards compatibility in configs
|
||||
MatchType MatchType `yaml:"match_type"`
|
||||
GlobDisableOrdering bool `yaml:"glob_disable_ordering"`
|
||||
Ttl time.Duration `yaml:"ttl"`
|
||||
SummaryOptions SummaryOptions `yaml:"summary_options"`
|
||||
HistogramOptions HistogramOptions `yaml:"histogram_options"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML is a custom unmarshal function to allow use of deprecated config keys
|
||||
// observer_type will override timer_type
|
||||
func (d *MapperConfigDefaults) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var tmp mapperConfigDefaultsAlias
|
||||
if err := unmarshal(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy defaults
|
||||
d.ObserverType = tmp.ObserverType
|
||||
d.MatchType = tmp.MatchType
|
||||
d.GlobDisableOrdering = tmp.GlobDisableOrdering
|
||||
d.Ttl = tmp.Ttl
|
||||
d.SummaryOptions = tmp.SummaryOptions
|
||||
d.HistogramOptions = tmp.HistogramOptions
|
||||
|
||||
// Use deprecated TimerType if necessary
|
||||
if tmp.ObserverType == "" {
|
||||
d.ObserverType = tmp.TimerType
|
||||
}
|
||||
|
||||
// Use deprecated quantiles if necessary
|
||||
if len(tmp.SummaryOptions.Quantiles) == 0 && len(tmp.Quantiles) > 0 {
|
||||
d.SummaryOptions = SummaryOptions{Quantiles: tmp.Quantiles}
|
||||
}
|
||||
|
||||
// Use deprecated buckets if necessary
|
||||
if len(tmp.HistogramOptions.Buckets) == 0 && len(tmp.Buckets) > 0 {
|
||||
d.HistogramOptions = HistogramOptions{Buckets: tmp.Buckets}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
1762
pkg/mapper/mapper_test.go
Normal file
1762
pkg/mapper/mapper_test.go
Normal file
File diff suppressed because it is too large
Load diff
102
pkg/mapper/mapping.go
Normal file
102
pkg/mapper/mapping.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2020 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either xpress or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper/fsm"
|
||||
)
|
||||
|
||||
type MetricMapping struct {
|
||||
Match string `yaml:"match"`
|
||||
Name string `yaml:"name"`
|
||||
nameFormatter *fsm.TemplateFormatter
|
||||
regex *regexp.Regexp
|
||||
Labels prometheus.Labels `yaml:"labels"`
|
||||
HonorLabels bool `yaml:"honor_labels"`
|
||||
labelKeys []string
|
||||
labelFormatters []*fsm.TemplateFormatter
|
||||
ObserverType ObserverType `yaml:"observer_type"`
|
||||
TimerType ObserverType `yaml:"timer_type,omitempty"` // DEPRECATED - field only present to preserve backwards compatibility in configs. Always empty
|
||||
LegacyBuckets []float64 `yaml:"buckets"`
|
||||
LegacyQuantiles []MetricObjective `yaml:"quantiles"`
|
||||
MatchType MatchType `yaml:"match_type"`
|
||||
HelpText string `yaml:"help"`
|
||||
Action ActionType `yaml:"action"`
|
||||
MatchMetricType MetricType `yaml:"match_metric_type"`
|
||||
Ttl time.Duration `yaml:"ttl"`
|
||||
SummaryOptions *SummaryOptions `yaml:"summary_options"`
|
||||
HistogramOptions *HistogramOptions `yaml:"histogram_options"`
|
||||
Scale MaybeFloat64 `yaml:"scale"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML is a custom unmarshal function to allow use of deprecated config keys
|
||||
// observer_type will override timer_type
|
||||
func (m *MetricMapping) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type MetricMappingAlias MetricMapping
|
||||
var tmp MetricMappingAlias
|
||||
if err := unmarshal(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy defaults
|
||||
m.Match = tmp.Match
|
||||
m.Name = tmp.Name
|
||||
m.Labels = tmp.Labels
|
||||
m.HonorLabels = tmp.HonorLabels
|
||||
m.ObserverType = tmp.ObserverType
|
||||
m.LegacyBuckets = tmp.LegacyBuckets
|
||||
m.LegacyQuantiles = tmp.LegacyQuantiles
|
||||
m.MatchType = tmp.MatchType
|
||||
m.HelpText = tmp.HelpText
|
||||
m.Action = tmp.Action
|
||||
m.MatchMetricType = tmp.MatchMetricType
|
||||
m.Ttl = tmp.Ttl
|
||||
m.SummaryOptions = tmp.SummaryOptions
|
||||
m.HistogramOptions = tmp.HistogramOptions
|
||||
m.Scale = tmp.Scale
|
||||
|
||||
// Use deprecated TimerType if necessary
|
||||
if tmp.ObserverType == "" {
|
||||
m.ObserverType = tmp.TimerType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MaybeFloat64 struct {
|
||||
Set bool
|
||||
Val float64
|
||||
}
|
||||
|
||||
func (m *MaybeFloat64) MarshalYAML() (interface{}, error) {
|
||||
if m.Set {
|
||||
return m.Val, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MaybeFloat64) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var tmp float64
|
||||
if err := unmarshal(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Val = tmp
|
||||
m.Set = true
|
||||
return nil
|
||||
}
|
41
pkg/mapper/match.go
Normal file
41
pkg/mapper/match.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import "fmt"
|
||||
|
||||
type MatchType string
|
||||
|
||||
const (
|
||||
MatchTypeGlob MatchType = "glob"
|
||||
MatchTypeRegex MatchType = "regex"
|
||||
MatchTypeDefault MatchType = ""
|
||||
)
|
||||
|
||||
func (t *MatchType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var v string
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch MatchType(v) {
|
||||
case MatchTypeRegex:
|
||||
*t = MatchTypeRegex
|
||||
case MatchTypeGlob, MatchTypeDefault:
|
||||
*t = MatchTypeGlob
|
||||
default:
|
||||
return fmt.Errorf("invalid match type %q", v)
|
||||
}
|
||||
return nil
|
||||
}
|
46
pkg/mapper/metric_type.go
Normal file
46
pkg/mapper/metric_type.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import "fmt"
|
||||
|
||||
type MetricType string
|
||||
|
||||
const (
|
||||
MetricTypeCounter MetricType = "counter"
|
||||
MetricTypeGauge MetricType = "gauge"
|
||||
MetricTypeObserver MetricType = "observer"
|
||||
MetricTypeTimer MetricType = "timer" // DEPRECATED
|
||||
)
|
||||
|
||||
func (m *MetricType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var v string
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch MetricType(v) {
|
||||
case MetricTypeCounter:
|
||||
*m = MetricTypeCounter
|
||||
case MetricTypeGauge:
|
||||
*m = MetricTypeGauge
|
||||
case MetricTypeObserver:
|
||||
*m = MetricTypeObserver
|
||||
case MetricTypeTimer:
|
||||
*m = MetricTypeObserver
|
||||
default:
|
||||
return fmt.Errorf("invalid metric type '%s'", v)
|
||||
}
|
||||
return nil
|
||||
}
|
41
pkg/mapper/observer.go
Normal file
41
pkg/mapper/observer.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapper
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ObserverType string
|
||||
|
||||
const (
|
||||
ObserverTypeHistogram ObserverType = "histogram"
|
||||
ObserverTypeSummary ObserverType = "summary"
|
||||
ObserverTypeDefault ObserverType = ""
|
||||
)
|
||||
|
||||
func (t *ObserverType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var v string
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch ObserverType(v) {
|
||||
case ObserverTypeHistogram:
|
||||
*t = ObserverTypeHistogram
|
||||
case ObserverTypeSummary, ObserverTypeDefault:
|
||||
*t = ObserverTypeSummary
|
||||
default:
|
||||
return fmt.Errorf("invalid observer type '%s'", v)
|
||||
}
|
||||
return nil
|
||||
}
|
103
pkg/mappercache/lru/lru.go
Normal file
103
pkg/mappercache/lru/lru.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/golang/groupcache/lru"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/mappercache"
|
||||
)
|
||||
|
||||
type metricMapperLRUCache struct {
|
||||
cache *lruCache
|
||||
metrics *mappercache.CacheMetrics
|
||||
}
|
||||
|
||||
func NewMetricMapperLRUCache(reg prometheus.Registerer, size int) (*metricMapperLRUCache, error) {
|
||||
if size <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
metrics := mappercache.NewCacheMetrics(reg)
|
||||
cache := newLruCache(size)
|
||||
|
||||
return &metricMapperLRUCache{metrics: metrics, cache: cache}, nil
|
||||
}
|
||||
|
||||
func (m *metricMapperLRUCache) Get(metricKey string) (interface{}, bool) {
|
||||
m.metrics.CacheGetsTotal.Inc()
|
||||
if result, ok := m.cache.Get(metricKey); ok {
|
||||
m.metrics.CacheHitsTotal.Inc()
|
||||
return result, true
|
||||
} else {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (m *metricMapperLRUCache) Add(metricKey string, result interface{}) {
|
||||
go m.trackCacheLength()
|
||||
m.cache.Add(metricKey, result)
|
||||
}
|
||||
|
||||
func (m *metricMapperLRUCache) trackCacheLength() {
|
||||
m.metrics.CacheLength.Set(float64(m.cache.Len()))
|
||||
}
|
||||
|
||||
func (m *metricMapperLRUCache) Reset() {
|
||||
m.cache.Clear()
|
||||
m.metrics.CacheLength.Set(0)
|
||||
}
|
||||
|
||||
type lruCache struct {
|
||||
cache *lru.Cache
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func newLruCache(maxEntries int) *lruCache {
|
||||
return &lruCache{
|
||||
cache: lru.New(maxEntries),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lruCache) Get(key string) (interface{}, bool) {
|
||||
l.lock.RLock()
|
||||
defer l.lock.RUnlock()
|
||||
|
||||
return l.cache.Get(key)
|
||||
}
|
||||
|
||||
func (l *lruCache) Add(key string, value interface{}) {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
l.cache.Add(key, value)
|
||||
}
|
||||
|
||||
func (l *lruCache) Len() int {
|
||||
l.lock.RLock()
|
||||
defer l.lock.RUnlock()
|
||||
|
||||
return l.cache.Len()
|
||||
}
|
||||
|
||||
func (l *lruCache) Clear() {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
l.cache.Clear()
|
||||
}
|
52
pkg/mappercache/metrics.go
Normal file
52
pkg/mappercache/metrics.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mappercache
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
type CacheMetrics struct {
|
||||
CacheLength prometheus.Gauge
|
||||
CacheGetsTotal prometheus.Counter
|
||||
CacheHitsTotal prometheus.Counter
|
||||
}
|
||||
|
||||
func NewCacheMetrics(reg prometheus.Registerer) *CacheMetrics {
|
||||
var m CacheMetrics
|
||||
|
||||
m.CacheLength = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "statsd_metric_mapper_cache_length",
|
||||
Help: "The count of unique metrics currently cached.",
|
||||
},
|
||||
)
|
||||
m.CacheGetsTotal = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_metric_mapper_cache_gets_total",
|
||||
Help: "The count of total metric cache gets.",
|
||||
},
|
||||
)
|
||||
m.CacheHitsTotal = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_metric_mapper_cache_hits_total",
|
||||
Help: "The count of total metric cache hits.",
|
||||
},
|
||||
)
|
||||
|
||||
if reg != nil {
|
||||
reg.MustRegister(m.CacheLength)
|
||||
reg.MustRegister(m.CacheGetsTotal)
|
||||
reg.MustRegister(m.CacheHitsTotal)
|
||||
}
|
||||
return &m
|
||||
}
|
83
pkg/mappercache/randomreplacement/randomreplacement.go
Normal file
83
pkg/mappercache/randomreplacement/randomreplacement.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package randomreplacement
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/mappercache"
|
||||
)
|
||||
|
||||
type metricMapperRRCache struct {
|
||||
lock sync.RWMutex
|
||||
size int
|
||||
items map[string]interface{}
|
||||
metrics *mappercache.CacheMetrics
|
||||
}
|
||||
|
||||
func NewMetricMapperRRCache(reg prometheus.Registerer, size int) (*metricMapperRRCache, error) {
|
||||
if size <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
metrics := mappercache.NewCacheMetrics(reg)
|
||||
c := &metricMapperRRCache{
|
||||
items: make(map[string]interface{}, size+1),
|
||||
size: size,
|
||||
metrics: metrics,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (m *metricMapperRRCache) Get(metricKey string) (interface{}, bool) {
|
||||
m.lock.RLock()
|
||||
result, ok := m.items[metricKey]
|
||||
m.lock.RUnlock()
|
||||
|
||||
return result, ok
|
||||
}
|
||||
|
||||
func (m *metricMapperRRCache) Add(metricKey string, result interface{}) {
|
||||
go m.trackCacheLength()
|
||||
|
||||
m.lock.Lock()
|
||||
|
||||
m.items[metricKey] = result
|
||||
|
||||
// evict an item if needed
|
||||
if len(m.items) > m.size {
|
||||
for k := range m.items {
|
||||
delete(m.items, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
m.lock.Unlock()
|
||||
}
|
||||
|
||||
func (m *metricMapperRRCache) Reset() {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.items = make(map[string]interface{}, m.size+1)
|
||||
m.metrics.CacheLength.Set(0)
|
||||
}
|
||||
|
||||
func (m *metricMapperRRCache) trackCacheLength() {
|
||||
m.lock.RLock()
|
||||
length := len(m.items)
|
||||
m.lock.RUnlock()
|
||||
m.metrics.CacheLength.Set(float64(length))
|
||||
}
|
67
pkg/metrics/metrics.go
Normal file
67
pkg/metrics/metrics.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type MetricType int
|
||||
|
||||
const (
|
||||
CounterMetricType MetricType = iota
|
||||
GaugeMetricType
|
||||
SummaryMetricType
|
||||
HistogramMetricType
|
||||
)
|
||||
|
||||
type NameHash uint64
|
||||
|
||||
type ValueHash uint64
|
||||
|
||||
type LabelHash struct {
|
||||
// This is a hash over the label names
|
||||
Names NameHash
|
||||
// This is a hash over the label names + label values
|
||||
Values ValueHash
|
||||
}
|
||||
|
||||
type MetricHolder interface{}
|
||||
|
||||
type VectorHolder interface {
|
||||
Delete(label prometheus.Labels) bool
|
||||
}
|
||||
|
||||
type Vector struct {
|
||||
Holder VectorHolder
|
||||
RefCount uint64
|
||||
}
|
||||
|
||||
type Metric struct {
|
||||
MetricType MetricType
|
||||
// Vectors key is the hash of the label names
|
||||
Vectors map[NameHash]*Vector
|
||||
// Metrics key is a hash of the label names + label values
|
||||
Metrics map[ValueHash]*RegisteredMetric
|
||||
}
|
||||
|
||||
type RegisteredMetric struct {
|
||||
LastRegisteredAt time.Time
|
||||
Labels prometheus.Labels
|
||||
TTL time.Duration
|
||||
Metric MetricHolder
|
||||
VecKey NameHash
|
||||
}
|
428
pkg/registry/registry.go
Normal file
428
pkg/registry/registry.go
Normal file
|
@ -0,0 +1,428 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package registry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/clock"
|
||||
"github.com/prometheus/statsd_exporter/pkg/mapper"
|
||||
"github.com/prometheus/statsd_exporter/pkg/metrics"
|
||||
)
|
||||
|
||||
// uncheckedCollector wraps a Collector but its Describe method yields no Desc.
|
||||
// This allows incoming metrics to have inconsistent label sets
|
||||
type uncheckedCollector struct {
|
||||
c prometheus.Collector
|
||||
}
|
||||
|
||||
func (u uncheckedCollector) Describe(_ chan<- *prometheus.Desc) {}
|
||||
func (u uncheckedCollector) Collect(c chan<- prometheus.Metric) {
|
||||
u.c.Collect(c)
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
Registerer prometheus.Registerer
|
||||
Metrics map[string]metrics.Metric
|
||||
Mapper *mapper.MetricMapper
|
||||
// The below value and label variables are allocated in the registry struct
|
||||
// so that we don't have to allocate them every time have to compute a label
|
||||
// hash.
|
||||
ValueBuf, NameBuf bytes.Buffer
|
||||
Hasher hash.Hash64
|
||||
}
|
||||
|
||||
func NewRegistry(reg prometheus.Registerer, mapper *mapper.MetricMapper) *Registry {
|
||||
return &Registry{
|
||||
Registerer: reg,
|
||||
Metrics: make(map[string]metrics.Metric),
|
||||
Mapper: mapper,
|
||||
Hasher: fnv.New64a(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) MetricConflicts(metricName string, metricType metrics.MetricType) bool {
|
||||
vector, hasMetrics := r.Metrics[metricName]
|
||||
if !hasMetrics {
|
||||
// No metrics.Metric with this name exists
|
||||
return false
|
||||
}
|
||||
|
||||
if vector.MetricType == metricType {
|
||||
// We've found a copy of this metrics.Metric with this type, but different
|
||||
// labels, so it's safe to create a new one.
|
||||
return false
|
||||
}
|
||||
|
||||
// The metrics.Metric exists, but it's of a different type than we're trying to
|
||||
// create.
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Registry) StoreCounter(metricName string, hash metrics.LabelHash, labels prometheus.Labels, vec *prometheus.CounterVec, c prometheus.Counter, ttl time.Duration) {
|
||||
r.Store(metricName, hash, labels, vec, c, metrics.CounterMetricType, ttl)
|
||||
}
|
||||
|
||||
func (r *Registry) StoreGauge(metricName string, hash metrics.LabelHash, labels prometheus.Labels, vec *prometheus.GaugeVec, g prometheus.Gauge, ttl time.Duration) {
|
||||
r.Store(metricName, hash, labels, vec, g, metrics.GaugeMetricType, ttl)
|
||||
}
|
||||
|
||||
func (r *Registry) StoreHistogram(metricName string, hash metrics.LabelHash, labels prometheus.Labels, vec *prometheus.HistogramVec, o prometheus.Observer, ttl time.Duration) {
|
||||
r.Store(metricName, hash, labels, vec, o, metrics.HistogramMetricType, ttl)
|
||||
}
|
||||
|
||||
func (r *Registry) StoreSummary(metricName string, hash metrics.LabelHash, labels prometheus.Labels, vec *prometheus.SummaryVec, o prometheus.Observer, ttl time.Duration) {
|
||||
r.Store(metricName, hash, labels, vec, o, metrics.SummaryMetricType, ttl)
|
||||
}
|
||||
|
||||
func (r *Registry) Store(metricName string, hash metrics.LabelHash, labels prometheus.Labels, vh metrics.VectorHolder, mh metrics.MetricHolder, metricType metrics.MetricType, ttl time.Duration) {
|
||||
metric, hasMetrics := r.Metrics[metricName]
|
||||
if !hasMetrics {
|
||||
metric.MetricType = metricType
|
||||
metric.Vectors = make(map[metrics.NameHash]*metrics.Vector)
|
||||
metric.Metrics = make(map[metrics.ValueHash]*metrics.RegisteredMetric)
|
||||
|
||||
r.Metrics[metricName] = metric
|
||||
}
|
||||
|
||||
v, ok := metric.Vectors[hash.Names]
|
||||
if !ok {
|
||||
v = &metrics.Vector{Holder: vh}
|
||||
metric.Vectors[hash.Names] = v
|
||||
}
|
||||
|
||||
now := clock.Now()
|
||||
rm, ok := metric.Metrics[hash.Values]
|
||||
if !ok {
|
||||
rm = &metrics.RegisteredMetric{
|
||||
LastRegisteredAt: now,
|
||||
Labels: labels,
|
||||
TTL: ttl,
|
||||
Metric: mh,
|
||||
VecKey: hash.Names,
|
||||
}
|
||||
metric.Metrics[hash.Values] = rm
|
||||
v.RefCount++
|
||||
return
|
||||
}
|
||||
rm.LastRegisteredAt = now
|
||||
// Update ttl from mapping
|
||||
rm.TTL = ttl
|
||||
}
|
||||
|
||||
func (r *Registry) Get(metricName string, hash metrics.LabelHash, metricType metrics.MetricType) (metrics.VectorHolder, metrics.MetricHolder) {
|
||||
metric, hasMetric := r.Metrics[metricName]
|
||||
|
||||
if !hasMetric {
|
||||
return nil, nil
|
||||
}
|
||||
if metric.MetricType != metricType {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rm, ok := metric.Metrics[hash.Values]
|
||||
if ok {
|
||||
now := clock.Now()
|
||||
rm.LastRegisteredAt = now
|
||||
return metric.Vectors[hash.Names].Holder, rm.Metric
|
||||
}
|
||||
|
||||
vector, ok := metric.Vectors[hash.Names]
|
||||
if ok {
|
||||
return vector.Holder, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Registry) GetCounter(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Counter, error) {
|
||||
hash, labelNames := r.HashLabels(labels)
|
||||
vh, mh := r.Get(metricName, hash, metrics.CounterMetricType)
|
||||
if mh != nil {
|
||||
return mh.(prometheus.Counter), nil
|
||||
}
|
||||
|
||||
if r.MetricConflicts(metricName, metrics.CounterMetricType) {
|
||||
return nil, fmt.Errorf("metric with name %s is already registered", metricName)
|
||||
}
|
||||
|
||||
err := r.checkHistogramNameCollision(metricName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var counterVec *prometheus.CounterVec
|
||||
if vh == nil {
|
||||
metricsCount.WithLabelValues("counter").Inc()
|
||||
counterVec = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: metricName,
|
||||
Help: help,
|
||||
}, labelNames)
|
||||
|
||||
if err := r.Registerer.Register(uncheckedCollector{counterVec}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
counterVec = vh.(*prometheus.CounterVec)
|
||||
}
|
||||
|
||||
var counter prometheus.Counter
|
||||
if counter, err = counterVec.GetMetricWith(labels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.StoreCounter(metricName, hash, labels, counterVec, counter, mapping.Ttl)
|
||||
|
||||
return counter, nil
|
||||
}
|
||||
|
||||
func (r *Registry) checkHistogramNameCollision(metricName string) error {
|
||||
histogramSuffixes := []string{"_bucket", "_count", "_sum"}
|
||||
for _, suffix := range histogramSuffixes {
|
||||
if strings.HasSuffix(metricName, suffix) {
|
||||
if r.MetricConflicts(strings.TrimSuffix(metricName, suffix), metrics.CounterMetricType) {
|
||||
return fmt.Errorf("metric with name %s is already registered", metricName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Registry) GetGauge(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Gauge, error) {
|
||||
hash, labelNames := r.HashLabels(labels)
|
||||
vh, mh := r.Get(metricName, hash, metrics.GaugeMetricType)
|
||||
if mh != nil {
|
||||
return mh.(prometheus.Gauge), nil
|
||||
}
|
||||
|
||||
if r.MetricConflicts(metricName, metrics.GaugeMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
|
||||
err := r.checkHistogramNameCollision(metricName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
|
||||
var gaugeVec *prometheus.GaugeVec
|
||||
if vh == nil {
|
||||
metricsCount.WithLabelValues("gauge").Inc()
|
||||
gaugeVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: metricName,
|
||||
Help: help,
|
||||
}, labelNames)
|
||||
|
||||
if err := r.Registerer.Register(uncheckedCollector{gaugeVec}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
gaugeVec = vh.(*prometheus.GaugeVec)
|
||||
}
|
||||
|
||||
var gauge prometheus.Gauge
|
||||
if gauge, err = gaugeVec.GetMetricWith(labels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.StoreGauge(metricName, hash, labels, gaugeVec, gauge, mapping.Ttl)
|
||||
|
||||
return gauge, nil
|
||||
}
|
||||
|
||||
func (r *Registry) GetHistogram(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Observer, error) {
|
||||
hash, labelNames := r.HashLabels(labels)
|
||||
vh, mh := r.Get(metricName, hash, metrics.HistogramMetricType)
|
||||
if mh != nil {
|
||||
return mh.(prometheus.Observer), nil
|
||||
}
|
||||
|
||||
if r.MetricConflicts(metricName, metrics.HistogramMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
if r.MetricConflicts(metricName+"_sum", metrics.HistogramMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
if r.MetricConflicts(metricName+"_count", metrics.HistogramMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
if r.MetricConflicts(metricName+"_bucket", metrics.HistogramMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
|
||||
var histogramVec *prometheus.HistogramVec
|
||||
if vh == nil {
|
||||
metricsCount.WithLabelValues("histogram").Inc()
|
||||
buckets := r.Mapper.Defaults.HistogramOptions.Buckets
|
||||
if mapping.HistogramOptions != nil && len(mapping.HistogramOptions.Buckets) > 0 {
|
||||
buckets = mapping.HistogramOptions.Buckets
|
||||
}
|
||||
|
||||
bucketFactor := r.Mapper.Defaults.HistogramOptions.NativeHistogramBucketFactor
|
||||
if mapping.HistogramOptions != nil && mapping.HistogramOptions.NativeHistogramBucketFactor > 0 {
|
||||
bucketFactor = mapping.HistogramOptions.NativeHistogramBucketFactor
|
||||
}
|
||||
|
||||
maxBuckets := r.Mapper.Defaults.HistogramOptions.NativeHistogramMaxBuckets
|
||||
if mapping.HistogramOptions != nil && mapping.HistogramOptions.NativeHistogramMaxBuckets > 0 {
|
||||
maxBuckets = mapping.HistogramOptions.NativeHistogramMaxBuckets
|
||||
}
|
||||
histogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: metricName,
|
||||
Help: help,
|
||||
Buckets: buckets,
|
||||
NativeHistogramBucketFactor: bucketFactor,
|
||||
NativeHistogramMaxBucketNumber: maxBuckets,
|
||||
}, labelNames)
|
||||
|
||||
if err := r.Registerer.Register(uncheckedCollector{histogramVec}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
histogramVec = vh.(*prometheus.HistogramVec)
|
||||
}
|
||||
|
||||
var observer prometheus.Observer
|
||||
var err error
|
||||
if observer, err = histogramVec.GetMetricWith(labels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.StoreHistogram(metricName, hash, labels, histogramVec, observer, mapping.Ttl)
|
||||
|
||||
return observer, nil
|
||||
}
|
||||
|
||||
func (r *Registry) GetSummary(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping, metricsCount *prometheus.GaugeVec) (prometheus.Observer, error) {
|
||||
hash, labelNames := r.HashLabels(labels)
|
||||
vh, mh := r.Get(metricName, hash, metrics.SummaryMetricType)
|
||||
if mh != nil {
|
||||
return mh.(prometheus.Observer), nil
|
||||
}
|
||||
|
||||
if r.MetricConflicts(metricName, metrics.SummaryMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
if r.MetricConflicts(metricName+"_sum", metrics.SummaryMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
if r.MetricConflicts(metricName+"_count", metrics.SummaryMetricType) {
|
||||
return nil, fmt.Errorf("metrics.Metric with name %s is already registered", metricName)
|
||||
}
|
||||
|
||||
var summaryVec *prometheus.SummaryVec
|
||||
if vh == nil {
|
||||
metricsCount.WithLabelValues("summary").Inc()
|
||||
quantiles := r.Mapper.Defaults.SummaryOptions.Quantiles
|
||||
if mapping != nil && mapping.SummaryOptions != nil && len(mapping.SummaryOptions.Quantiles) > 0 {
|
||||
quantiles = mapping.SummaryOptions.Quantiles
|
||||
}
|
||||
|
||||
summaryOptions := mapper.SummaryOptions{
|
||||
MaxAge: r.Mapper.Defaults.SummaryOptions.MaxAge,
|
||||
AgeBuckets: r.Mapper.Defaults.SummaryOptions.AgeBuckets,
|
||||
BufCap: r.Mapper.Defaults.SummaryOptions.BufCap,
|
||||
}
|
||||
|
||||
if mapping != nil && mapping.SummaryOptions != nil {
|
||||
summaryOptions = *mapping.SummaryOptions
|
||||
}
|
||||
|
||||
objectives := make(map[float64]float64)
|
||||
for _, q := range quantiles {
|
||||
objectives[q.Quantile] = q.Error
|
||||
}
|
||||
// In the case of no mapping file, explicitly define the default quantiles
|
||||
if len(objectives) == 0 {
|
||||
objectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
|
||||
}
|
||||
summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{
|
||||
Name: metricName,
|
||||
Help: help,
|
||||
Objectives: objectives,
|
||||
MaxAge: summaryOptions.MaxAge,
|
||||
AgeBuckets: summaryOptions.AgeBuckets,
|
||||
BufCap: summaryOptions.BufCap,
|
||||
}, labelNames)
|
||||
|
||||
if err := r.Registerer.Register(uncheckedCollector{summaryVec}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
summaryVec = vh.(*prometheus.SummaryVec)
|
||||
}
|
||||
|
||||
var observer prometheus.Observer
|
||||
var err error
|
||||
if observer, err = summaryVec.GetMetricWith(labels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.StoreSummary(metricName, hash, labels, summaryVec, observer, mapping.Ttl)
|
||||
|
||||
return observer, nil
|
||||
}
|
||||
|
||||
func (r *Registry) RemoveStaleMetrics() {
|
||||
now := clock.Now()
|
||||
// delete timeseries with expired ttl
|
||||
for _, metric := range r.Metrics {
|
||||
for hash, rm := range metric.Metrics {
|
||||
if rm.TTL == 0 {
|
||||
continue
|
||||
}
|
||||
if rm.LastRegisteredAt.Add(rm.TTL).Before(now) {
|
||||
metric.Vectors[rm.VecKey].Holder.Delete(rm.Labels)
|
||||
metric.Vectors[rm.VecKey].RefCount--
|
||||
delete(metric.Metrics, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates a hash of both the label names and values.
|
||||
func (r *Registry) HashLabels(labels prometheus.Labels) (metrics.LabelHash, []string) {
|
||||
r.Hasher.Reset()
|
||||
r.NameBuf.Reset()
|
||||
r.ValueBuf.Reset()
|
||||
labelNames := make([]string, 0, len(labels))
|
||||
|
||||
for labelName := range labels {
|
||||
labelNames = append(labelNames, labelName)
|
||||
}
|
||||
sort.Strings(labelNames)
|
||||
|
||||
r.ValueBuf.WriteByte(model.SeparatorByte)
|
||||
for _, labelName := range labelNames {
|
||||
r.ValueBuf.WriteString(labels[labelName])
|
||||
r.ValueBuf.WriteByte(model.SeparatorByte)
|
||||
|
||||
r.NameBuf.WriteString(labelName)
|
||||
r.NameBuf.WriteByte(model.SeparatorByte)
|
||||
}
|
||||
|
||||
lh := metrics.LabelHash{}
|
||||
r.Hasher.Write(r.NameBuf.Bytes())
|
||||
lh.Names = metrics.NameHash(r.Hasher.Sum64())
|
||||
|
||||
// Now add the values to the names we've already hashed.
|
||||
r.Hasher.Write(r.ValueBuf.Bytes())
|
||||
lh.Values = metrics.ValueHash(r.Hasher.Sum64())
|
||||
|
||||
return lh, labelNames
|
||||
}
|
167
pkg/relay/relay.go
Normal file
167
pkg/relay/relay.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package relay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/statsd_exporter/pkg/clock"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/statsd_exporter/pkg/level"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
addr *net.UDPAddr
|
||||
bufferChannel chan []byte
|
||||
conn *net.UDPConn
|
||||
logger log.Logger
|
||||
packetLength uint
|
||||
|
||||
packetsTotal prometheus.Counter
|
||||
longLinesTotal prometheus.Counter
|
||||
relayedLinesTotal prometheus.Counter
|
||||
}
|
||||
|
||||
var (
|
||||
relayPacketsTotal = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_relay_packets_total",
|
||||
Help: "The number of StatsD packets relayed.",
|
||||
},
|
||||
[]string{"target"},
|
||||
)
|
||||
relayLongLinesTotal = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_relay_long_lines_total",
|
||||
Help: "The number lines that were too long to relay.",
|
||||
},
|
||||
[]string{"target"},
|
||||
)
|
||||
relayLinesRelayedTotal = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_relay_lines_relayed_total",
|
||||
Help: "The number of lines that were buffered to be relayed.",
|
||||
},
|
||||
[]string{"target"},
|
||||
)
|
||||
)
|
||||
|
||||
// NewRelay creates a statsd UDP relay. It can be used to send copies of statsd raw
|
||||
// lines to a separate service.
|
||||
func NewRelay(l log.Logger, target string, packetLength uint) (*Relay, error) {
|
||||
addr, err := net.ResolveUDPAddr("udp", target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to resolve target %s, err: %w", target, err)
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to listen on UDP, err: %w", err)
|
||||
}
|
||||
|
||||
c := make(chan []byte, 100)
|
||||
|
||||
r := Relay{
|
||||
addr: addr,
|
||||
bufferChannel: c,
|
||||
conn: conn,
|
||||
logger: l,
|
||||
packetLength: packetLength,
|
||||
|
||||
packetsTotal: relayPacketsTotal.WithLabelValues(target),
|
||||
longLinesTotal: relayLongLinesTotal.WithLabelValues(target),
|
||||
relayedLinesTotal: relayLinesRelayedTotal.WithLabelValues(target),
|
||||
}
|
||||
|
||||
// Startup the UDP sender.
|
||||
go r.relayOutput()
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// relayOutput buffers statsd lines and sends them to the relay target.
|
||||
func (r *Relay) relayOutput() {
|
||||
var buffer bytes.Buffer
|
||||
var err error
|
||||
|
||||
relayInterval := clock.NewTicker(1 * time.Second)
|
||||
defer relayInterval.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-relayInterval.C:
|
||||
err = r.sendPacket(buffer.Bytes())
|
||||
if err != nil {
|
||||
level.Error(r.logger).Log("msg", "Error sending UDP packet", "error", err)
|
||||
return
|
||||
}
|
||||
// Clear out the buffer.
|
||||
buffer.Reset()
|
||||
case b := <-r.bufferChannel:
|
||||
if uint(len(b)+buffer.Len()) > r.packetLength {
|
||||
level.Debug(r.logger).Log("msg", "Buffer full, sending packet", "length", buffer.Len())
|
||||
err = r.sendPacket(buffer.Bytes())
|
||||
if err != nil {
|
||||
level.Error(r.logger).Log("msg", "Error sending UDP packet", "error", err)
|
||||
return
|
||||
}
|
||||
// Seed the new buffer with the new line.
|
||||
buffer.Reset()
|
||||
buffer.Write(b)
|
||||
} else {
|
||||
level.Debug(r.logger).Log("msg", "Adding line to buffer", "line", string(b))
|
||||
buffer.Write(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendPacket sends a single relay line to the destination target.
|
||||
func (r *Relay) sendPacket(buf []byte) error {
|
||||
if len(buf) == 0 {
|
||||
level.Debug(r.logger).Log("msg", "Empty buffer, nothing to send")
|
||||
return nil
|
||||
}
|
||||
level.Debug(r.logger).Log("msg", "Sending packet", "length", len(buf), "data", string(buf))
|
||||
_, err := r.conn.WriteToUDP(buf, r.addr)
|
||||
r.packetsTotal.Inc()
|
||||
return err
|
||||
}
|
||||
|
||||
// RelayLine processes a single statsd line and forwards it to the relay target.
|
||||
func (r *Relay) RelayLine(l string) {
|
||||
lineLength := uint(len(l))
|
||||
if lineLength == 0 {
|
||||
level.Debug(r.logger).Log("msg", "Empty line, not relaying")
|
||||
return
|
||||
}
|
||||
if lineLength > r.packetLength-1 {
|
||||
level.Warn(r.logger).Log("msg", "line too long, not relaying", "length", lineLength, "max", r.packetLength)
|
||||
r.longLinesTotal.Inc()
|
||||
return
|
||||
}
|
||||
level.Debug(r.logger).Log("msg", "Relaying line", "line", string(l))
|
||||
if !strings.HasSuffix(l, "\n") {
|
||||
l = l + "\n"
|
||||
}
|
||||
r.relayedLinesTotal.Inc()
|
||||
r.bufferChannel <- []byte(l)
|
||||
}
|
176
pkg/relay/relay_test.go
Normal file
176
pkg/relay/relay_test.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2022 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package relay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/prometheus/statsd_exporter/pkg/clock"
|
||||
"github.com/stvp/go-udp-testing"
|
||||
)
|
||||
|
||||
func TestRelay_RelayLine(t *testing.T) {
|
||||
type args struct {
|
||||
lines []string
|
||||
expected string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "multiple lines",
|
||||
args: args{
|
||||
lines: []string{"foo5:100|c|#tag1:bar,#tag2:baz", "foo2:200|c|#tag1:bar,#tag2:baz"},
|
||||
expected: "foo5:100|c|#tag1:bar,#tag2:baz\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
udp.SetAddr(":1160")
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tickerCh := make(chan time.Time)
|
||||
clock.ClockInstance = &clock.Clock{
|
||||
TickerCh: tickerCh,
|
||||
}
|
||||
clock.ClockInstance.Instant = time.Unix(0, 0)
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
r, err := NewRelay(
|
||||
logger,
|
||||
"localhost:1160",
|
||||
200,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Did not expect error while creating relay.")
|
||||
}
|
||||
|
||||
udp.ShouldReceive(t, tt.args.expected, func() {
|
||||
for _, line := range tt.args.lines {
|
||||
r.RelayLine(line)
|
||||
}
|
||||
|
||||
for goSchedTimes := 0; goSchedTimes < 1000; goSchedTimes++ {
|
||||
if len(r.bufferChannel) == 0 {
|
||||
break
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
|
||||
// Tick time forward to trigger a packet send.
|
||||
clock.ClockInstance.Instant = time.Unix(1, 10)
|
||||
clock.ClockInstance.TickerCh <- time.Unix(0, 0)
|
||||
})
|
||||
|
||||
metrics, err := prometheus.DefaultGatherer.Gather()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot gather from DefaultGatherer: %v", err)
|
||||
}
|
||||
|
||||
metricNames := map[string]float64{
|
||||
"statsd_exporter_relay_long_lines_total": 0,
|
||||
"statsd_exporter_relay_lines_relayed_total": float64(len(tt.args.lines)),
|
||||
}
|
||||
for metricName, expectedValue := range metricNames {
|
||||
metric := getFloat64(metrics, metricName, prometheus.Labels{"target": "localhost:1160"})
|
||||
|
||||
if metric == nil {
|
||||
t.Fatalf("Could not find time series with first label set for metric: %s", metricName)
|
||||
}
|
||||
if *metric != expectedValue {
|
||||
t.Errorf("Expected metric %s to be %f, got %f", metricName, expectedValue, *metric)
|
||||
}
|
||||
}
|
||||
|
||||
prometheus.Unregister(relayLongLinesTotal)
|
||||
prometheus.Unregister(relayLinesRelayedTotal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// getFloat64 search for metric by name in array of MetricFamily and then search a value by labels.
|
||||
// Method returns a value or nil if metric is not found.
|
||||
func getFloat64(metrics []*dto.MetricFamily, name string, labels prometheus.Labels) *float64 {
|
||||
var metricFamily *dto.MetricFamily
|
||||
for _, m := range metrics {
|
||||
if *m.Name == name {
|
||||
metricFamily = m
|
||||
break
|
||||
}
|
||||
}
|
||||
if metricFamily == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var metric *dto.Metric
|
||||
labelStr := fmt.Sprintf("%v", labels)
|
||||
for _, m := range metricFamily.Metric {
|
||||
l := labelPairsAsLabels(m.GetLabel())
|
||||
ls := fmt.Sprintf("%v", l)
|
||||
if labelStr == ls {
|
||||
metric = m
|
||||
break
|
||||
}
|
||||
}
|
||||
if metric == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var value float64
|
||||
if metric.Gauge != nil {
|
||||
value = metric.Gauge.GetValue()
|
||||
return &value
|
||||
}
|
||||
if metric.Counter != nil {
|
||||
value = metric.Counter.GetValue()
|
||||
return &value
|
||||
}
|
||||
if metric.Histogram != nil {
|
||||
value = metric.Histogram.GetSampleSum()
|
||||
return &value
|
||||
}
|
||||
if metric.Summary != nil {
|
||||
value = metric.Summary.GetSampleSum()
|
||||
return &value
|
||||
}
|
||||
if metric.Untyped != nil {
|
||||
value = metric.Untyped.GetValue()
|
||||
return &value
|
||||
}
|
||||
panic(fmt.Errorf("collected a non-gauge/counter/histogram/summary/untyped metric: %s", metric))
|
||||
}
|
||||
|
||||
func labelPairsAsLabels(pairs []*dto.LabelPair) (labels prometheus.Labels) {
|
||||
labels = prometheus.Labels{}
|
||||
for _, pair := range pairs {
|
||||
if pair.Name == nil {
|
||||
continue
|
||||
}
|
||||
value := ""
|
||||
if pair.Value != nil {
|
||||
value = *pair.Value
|
||||
}
|
||||
labels[*pair.Name] = value
|
||||
}
|
||||
return
|
||||
}
|
57
telemetry.go
57
telemetry.go
|
@ -1,57 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
eventStats = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_events_total",
|
||||
Help: "The total number of StatsD events seen.",
|
||||
},
|
||||
[]string{"type"},
|
||||
)
|
||||
eventsUnmapped = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_events_unmapped_total",
|
||||
Help: "The total number of StatsD events no mapping was found for.",
|
||||
})
|
||||
networkStats = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_packets_total",
|
||||
Help: "The total number of StatsD packets seen.",
|
||||
},
|
||||
[]string{"type"},
|
||||
)
|
||||
configLoads = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "statsd_exporter_config_reloads_total",
|
||||
Help: "The number of configuration reloads.",
|
||||
},
|
||||
[]string{"outcome"},
|
||||
)
|
||||
mappingsCount = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "statsd_exporter_loaded_mappings",
|
||||
Help: "The current number of configured metric mappings.",
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(eventStats)
|
||||
prometheus.MustRegister(networkStats)
|
||||
prometheus.MustRegister(configLoads)
|
||||
prometheus.MustRegister(mappingsCount)
|
||||
}
|
66
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
66
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
|
@ -1,66 +0,0 @@
|
|||
# 0.10.0
|
||||
|
||||
* feature: Add a test hook (#180)
|
||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||
* performance: avoid re-allocations on `WithFields` (#335)
|
||||
|
||||
# 0.9.0
|
||||
|
||||
* logrus/text_formatter: don't emit empty msg
|
||||
* logrus/hooks/airbrake: move out of main repository
|
||||
* logrus/hooks/sentry: move out of main repository
|
||||
* logrus/hooks/papertrail: move out of main repository
|
||||
* logrus/hooks/bugsnag: move out of main repository
|
||||
* logrus/core: run tests with `-race`
|
||||
* logrus/core: detect TTY based on `stderr`
|
||||
* logrus/core: support `WithError` on logger
|
||||
* logrus/core: Solaris support
|
||||
|
||||
# 0.8.7
|
||||
|
||||
* logrus/core: fix possible race (#216)
|
||||
* logrus/doc: small typo fixes and doc improvements
|
||||
|
||||
|
||||
# 0.8.6
|
||||
|
||||
* hooks/raven: allow passing an initialized client
|
||||
|
||||
# 0.8.5
|
||||
|
||||
* logrus/core: revert #208
|
||||
|
||||
# 0.8.4
|
||||
|
||||
* formatter/text: fix data race (#218)
|
||||
|
||||
# 0.8.3
|
||||
|
||||
* logrus/core: fix entry log level (#208)
|
||||
* logrus/core: improve performance of text formatter by 40%
|
||||
* logrus/core: expose `LevelHooks` type
|
||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||
* formatter/text: print structs more verbosely
|
||||
|
||||
# 0.8.2
|
||||
|
||||
* logrus: fix more Fatal family functions
|
||||
|
||||
# 0.8.1
|
||||
|
||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||
|
||||
# 0.8.0
|
||||
|
||||
* logrus: defaults to stderr instead of stdout
|
||||
* hooks/sentry: add special field for `*http.Request`
|
||||
* formatter/text: ignore Windows for colors
|
||||
|
||||
# 0.7.3
|
||||
|
||||
* formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
|
||||
* formatter/text: Add configuration option for time format (#158)
|
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
390
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
390
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
|
@ -1,390 +0,0 @@
|
|||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/Sirupsen/logrus?status.svg)](https://godoc.org/github.com/Sirupsen/logrus)
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
||||
many large deployments. The core API is unlikely to change much but please
|
||||
version control your Logrus to make sure you aren't fetching latest `master` on
|
||||
every build.**
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||
![Colored](http://i.imgur.com/PY7qMwd.png)
|
||||
|
||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
exit status 1
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Output to stderr instead of stdout, could also be a file.
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stderr
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging though logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||
`init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||
|
||||
| Hook | Description |
|
||||
| ----- | ----------- |
|
||||
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
||||
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
||||
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
||||
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
||||
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
||||
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
||||
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
||||
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
|
||||
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
||||
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
||||
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
|
||||
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
||||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events.
|
||||
|
||||
```go
|
||||
logrus.SetFormatter(&logstash.LogstashFormatter{Type: "application_name"})
|
||||
```
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Logger as an `io.Writer`
|
||||
|
||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||
|
||||
```go
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
|
||||
srv := http.Server{
|
||||
// create a stdlib log.Logger that writes to
|
||||
// logrus.Logger.
|
||||
ErrorLog: log.New(w, "", 0),
|
||||
}
|
||||
```
|
||||
|
||||
Each line written to that writer will be printed the usual way, using formatters
|
||||
and hooks. The level for those entries is `info`.
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotate(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
#### Tools
|
||||
|
||||
| Tool | Description |
|
||||
| ---- | ----------- |
|
||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
||||
|
||||
#### Testing
|
||||
|
||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||
|
||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
|
||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||
|
||||
```go
|
||||
logger, hook := NewNullLogger()
|
||||
logger.Error("Hello error")
|
||||
|
||||
assert.Equal(1, len(hook.Entries))
|
||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||
|
||||
hook.Reset()
|
||||
assert.Nil(hook.LastEntry())
|
||||
```
|
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 1,
|
||||
"size": 10,
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
|
||||
Output:
|
||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||
|
||||
For a full guide visit https://github.com/Sirupsen/logrus
|
||||
*/
|
||||
package logrus
|
264
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
264
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
|
@ -1,264 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Defines the key when adding errors using WithError.
|
||||
var ErrorKey = "error"
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||
// passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||
Level Level
|
||||
|
||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is three fields, give a little extra room
|
||||
Data: make(Fields, 5),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a reader for the entry, which is a proxy to the formatter.
|
||||
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
return bytes.NewBuffer(serialized), err
|
||||
}
|
||||
|
||||
// Returns the string representation from the reader and ultimately the
|
||||
// formatter.
|
||||
func (entry *Entry) String() (string, error) {
|
||||
reader, err := entry.Reader()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return reader.String(), err
|
||||
}
|
||||
|
||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||
func (entry *Entry) WithError(err error) *Entry {
|
||||
return entry.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// Add a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := make(Fields, len(entry.Data)+len(fields))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
for k, v := range fields {
|
||||
data[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data}
|
||||
}
|
||||
|
||||
// This function is not declared with a pointer value because otherwise
|
||||
// race conditions will occur when using multiple goroutines
|
||||
func (entry Entry) log(level Level, msg string) {
|
||||
entry.Time = time.Now()
|
||||
entry.Level = level
|
||||
entry.Message = msg
|
||||
|
||||
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
|
||||
reader, err := entry.Reader()
|
||||
if err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
|
||||
_, err = io.Copy(entry.Logger.Out, reader)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
|
||||
// To avoid Entry#log() returning a value that only would make sense for
|
||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||
// directly here.
|
||||
if level <= PanicLevel {
|
||||
panic(&entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warning(args ...interface{}) {
|
||||
entry.Warn(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||
}
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.Warn(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.Fatal(fmt.Sprintf(format, args...))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.Panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
if entry.Logger.Level >= DebugLevel {
|
||||
entry.Debug(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
if entry.Logger.Level >= InfoLevel {
|
||||
entry.Info(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
if entry.Logger.Level >= WarnLevel {
|
||||
entry.Warn(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.Error(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.Fatal(entry.sprintlnn(args...))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
if entry.Logger.Level >= PanicLevel {
|
||||
entry.Panic(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
}
|
193
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
193
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
|
@ -1,193 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
|
||||
func StandardLogger() *Logger {
|
||||
return std
|
||||
}
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Out = out
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter Formatter) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Formatter = formatter
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Level = level
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() Level {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
return std.Level
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook Hook) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||
func WithError(err error) *Entry {
|
||||
return std.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger.
|
||||
func Fatal(args ...interface{}) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger.
|
||||
func Fatalln(args ...interface{}) {
|
||||
std.Fatalln(args...)
|
||||
}
|
45
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
45
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
|
@ -1,45 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
const DefaultTimestampFormat = time.RFC3339
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields) {
|
||||
if t, ok := data["time"]; ok {
|
||||
data["fields.time"] = t
|
||||
}
|
||||
|
||||
if m, ok := data["msg"]; ok {
|
||||
data["fields.msg"] = m
|
||||
}
|
||||
|
||||
if l, ok := data["level"]; ok {
|
||||
data["fields.level"] = l
|
||||
}
|
||||
}
|
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
|
@ -1,34 +0,0 @@
|
|||
package logrus
|
||||
|
||||
// A hook to be fired when logging on the logging levels returned from
|
||||
// `Levels()` on your implementation of the interface. Note that this is not
|
||||
// fired in a goroutine or a channel with workers, you should handle such
|
||||
// functionality yourself if your call is non-blocking and you don't wish for
|
||||
// the logging calls for levels returned from `Levels()` to block.
|
||||
type Hook interface {
|
||||
Levels() []Level
|
||||
Fire(*Entry) error
|
||||
}
|
||||
|
||||
// Internal type for storing the hooks on a logger instance.
|
||||
type LevelHooks map[Level][]Hook
|
||||
|
||||
// Add a hook to an instance of logger. This is called with
|
||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||
func (hooks LevelHooks) Add(hook Hook) {
|
||||
for _, level := range hook.Levels() {
|
||||
hooks[level] = append(hooks[level], hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||
// appropriate hooks for a log entry.
|
||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||
for _, hook := range hooks[level] {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
41
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
41
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
|
@ -1,41 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
// Otherwise errors are ignored by `encoding/json`
|
||||
// https://github.com/Sirupsen/logrus/issues/137
|
||||
data[k] = v.Error()
|
||||
default:
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
prefixFieldClashes(data)
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
|
||||
data["time"] = entry.Time.Format(timestampFormat)
|
||||
data["msg"] = entry.Message
|
||||
data["level"] = entry.Level.String()
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
212
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
212
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
|
@ -1,212 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||
// something more adventorous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks LevelHooks
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged. `logrus.Debug` is useful in
|
||||
Level Level
|
||||
// Used to sync writing to the log.
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||
// instantiate your own:
|
||||
//
|
||||
// var log = &Logger{
|
||||
// Out: os.Stderr,
|
||||
// Formatter: new(JSONFormatter),
|
||||
// Hooks: make(LevelHooks),
|
||||
// Level: logrus.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(LevelHooks),
|
||||
Level: InfoLevel,
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a field to the log entry, note that you it doesn't log until you call
|
||||
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
||||
// If you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
return NewEntry(logger).WithField(key, value)
|
||||
}
|
||||
|
||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||
// each `Field`.
|
||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||
return NewEntry(logger).WithFields(fields)
|
||||
}
|
||||
|
||||
// Add an error as single field to the log entry. All it does is call
|
||||
// `WithError` for the given `error`.
|
||||
func (logger *Logger) WithError(err error) *Entry {
|
||||
return NewEntry(logger).WithError(err)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
if logger.Level >= DebugLevel {
|
||||
NewEntry(logger).Debugf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
if logger.Level >= InfoLevel {
|
||||
NewEntry(logger).Infof(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
NewEntry(logger).Printf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
if logger.Level >= ErrorLevel {
|
||||
NewEntry(logger).Errorf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
if logger.Level >= FatalLevel {
|
||||
NewEntry(logger).Fatalf(format, args...)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
if logger.Level >= PanicLevel {
|
||||
NewEntry(logger).Panicf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
if logger.Level >= DebugLevel {
|
||||
NewEntry(logger).Debug(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
if logger.Level >= InfoLevel {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
if logger.Level >= ErrorLevel {
|
||||
NewEntry(logger).Error(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
if logger.Level >= FatalLevel {
|
||||
NewEntry(logger).Fatal(args...)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
if logger.Level >= PanicLevel {
|
||||
NewEntry(logger).Panic(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
if logger.Level >= DebugLevel {
|
||||
NewEntry(logger).Debugln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
if logger.Level >= InfoLevel {
|
||||
NewEntry(logger).Infoln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
NewEntry(logger).Println(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
if logger.Level >= ErrorLevel {
|
||||
NewEntry(logger).Errorln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
if logger.Level >= FatalLevel {
|
||||
NewEntry(logger).Fatalln(args...)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
if logger.Level >= PanicLevel {
|
||||
NewEntry(logger).Panicln(args...)
|
||||
}
|
||||
}
|
143
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
143
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
|
@ -1,143 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Level type
|
||||
type Level uint8
|
||||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// A constant exposing all logging levels
|
||||
var AllLevels = []Level{
|
||||
PanicLevel,
|
||||
FatalLevel,
|
||||
ErrorLevel,
|
||||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
var (
|
||||
_ StdLogger = &log.Logger{}
|
||||
_ StdLogger = &Entry{}
|
||||
_ StdLogger = &Logger{}
|
||||
)
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||
// interface, this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
}
|
||||
|
||||
// The FieldLogger interface generalizes the Entry and Logger types
|
||||
type FieldLogger interface {
|
||||
WithField(key string, value interface{}) *Entry
|
||||
WithFields(fields Fields) *Entry
|
||||
WithError(err error) *Entry
|
||||
|
||||
Debugf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warningf(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
|
||||
Debug(args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Warn(args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Fatal(args ...interface{})
|
||||
Panic(args ...interface{})
|
||||
|
||||
Debugln(args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
Warningln(args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
}
|
9
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
9
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
|
@ -1,9 +0,0 @@
|
|||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios syscall.Termios
|
12
vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
12
vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
|
@ -1,12 +0,0 @@
|
|||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TCGETS
|
||||
|
||||
type Termios syscall.Termios
|
21
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
21
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
|
@ -1,21 +0,0 @@
|
|||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||
func IsTerminal() bool {
|
||||
fd := syscall.Stderr
|
||||
var termios Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
15
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
15
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
|
@ -1,15 +0,0 @@
|
|||
// +build solaris
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal() bool {
|
||||
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
27
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
27
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
|
@ -1,27 +0,0 @@
|
|||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
)
|
||||
|
||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||
func IsTerminal() bool {
|
||||
fd := syscall.Stderr
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
161
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
161
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
|
@ -1,161 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 34
|
||||
gray = 37
|
||||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
isTerminal bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
isTerminal = IsTerminal()
|
||||
}
|
||||
|
||||
func miniTS() int {
|
||||
return int(time.Since(baseTimestamp) / time.Second)
|
||||
}
|
||||
|
||||
type TextFormatter struct {
|
||||
// Set to true to bypass checking for a TTY before outputting colors.
|
||||
ForceColors bool
|
||||
|
||||
// Force disabling colors.
|
||||
DisableColors bool
|
||||
|
||||
// Disable timestamp logging. useful when output is redirected to logging
|
||||
// system that already adds timestamps.
|
||||
DisableTimestamp bool
|
||||
|
||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
||||
// the time passed since beginning of execution.
|
||||
FullTimestamp bool
|
||||
|
||||
// TimestampFormat to use for display when a full timestamp is printed
|
||||
TimestampFormat string
|
||||
|
||||
// The fields are sorted by default for a consistent output. For applications
|
||||
// that log extremely frequently and don't use the JSON formatter this may not
|
||||
// be desired.
|
||||
DisableSorting bool
|
||||
}
|
||||
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
var keys []string = make([]string, 0, len(entry.Data))
|
||||
for k := range entry.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
if !f.DisableSorting {
|
||||
sort.Strings(keys)
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
prefixFieldClashes(entry.Data)
|
||||
|
||||
isColorTerminal := isTerminal && (runtime.GOOS != "windows")
|
||||
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
if isColored {
|
||||
f.printColored(b, entry, keys, timestampFormat)
|
||||
} else {
|
||||
if !f.DisableTimestamp {
|
||||
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
|
||||
}
|
||||
f.appendKeyValue(b, "level", entry.Level.String())
|
||||
if entry.Message != "" {
|
||||
f.appendKeyValue(b, "msg", entry.Message)
|
||||
}
|
||||
for _, key := range keys {
|
||||
f.appendKeyValue(b, key, entry.Data[key])
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case DebugLevel:
|
||||
levelColor = gray
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||
|
||||
if !f.FullTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
||||
} else {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := entry.Data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuoting(text string) bool {
|
||||
for _, ch := range text {
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '.') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||
|
||||
b.WriteString(key)
|
||||
b.WriteByte('=')
|
||||
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if !needsQuoting(value) {
|
||||
b.WriteString(value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%q", value)
|
||||
}
|
||||
case error:
|
||||
errmsg := value.Error()
|
||||
if !needsQuoting(errmsg) {
|
||||
b.WriteString(errmsg)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%q", value)
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(b, value)
|
||||
}
|
||||
|
||||
b.WriteByte(' ')
|
||||
}
|
53
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
53
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
|
@ -1,53 +0,0 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func (logger *Logger) Writer() *io.PipeWriter {
|
||||
return logger.WriterLevel(InfoLevel)
|
||||
}
|
||||
|
||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
var printFunc func(args ...interface{})
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
printFunc = logger.Debug
|
||||
case InfoLevel:
|
||||
printFunc = logger.Info
|
||||
case WarnLevel:
|
||||
printFunc = logger.Warn
|
||||
case ErrorLevel:
|
||||
printFunc = logger.Error
|
||||
case FatalLevel:
|
||||
printFunc = logger.Fatal
|
||||
case PanicLevel:
|
||||
printFunc = logger.Panic
|
||||
default:
|
||||
printFunc = logger.Print
|
||||
}
|
||||
|
||||
go logger.writerScanner(reader, printFunc)
|
||||
runtime.SetFinalizer(writer, writerFinalizer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (logger *Logger) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
printFunc(scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
logger.Errorf("Error while reading from Writer: %s", err)
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
func writerFinalizer(writer *io.PipeWriter) {
|
||||
writer.Close()
|
||||
}
|
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
|
@ -1,20 +0,0 @@
|
|||
Copyright (C) 2013 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
File diff suppressed because it is too large
Load diff
292
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
292
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
|
@ -1,292 +0,0 @@
|
|||
// Package quantile computes approximate quantiles over an unbounded data
|
||||
// stream within low memory and CPU bounds.
|
||||
//
|
||||
// A small amount of accuracy is traded to achieve the above properties.
|
||||
//
|
||||
// Multiple streams can be merged before calling Query to generate a single set
|
||||
// of results. This is meaningful when the streams represent the same type of
|
||||
// data. See Merge and Samples.
|
||||
//
|
||||
// For more detailed information about the algorithm used, see:
|
||||
//
|
||||
// Effective Computation of Biased Quantiles over Data Streams
|
||||
//
|
||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sample holds an observed value and meta information for compression. JSON
|
||||
// tags have been added for convenience.
|
||||
type Sample struct {
|
||||
Value float64 `json:",string"`
|
||||
Width float64 `json:",string"`
|
||||
Delta float64 `json:",string"`
|
||||
}
|
||||
|
||||
// Samples represents a slice of samples. It implements sort.Interface.
|
||||
type Samples []Sample
|
||||
|
||||
func (a Samples) Len() int { return len(a) }
|
||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type invariant func(s *stream, r float64) float64
|
||||
|
||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the lower ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewLowBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * r
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the higher ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewHighBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * (s.n - r)
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||
// space and computation time. The targets map maps the desired quantiles to
|
||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||
// is guaranteed to be within (Quantile±Epsilon).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||
func NewTargeted(targets map[float64]float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
var m = math.MaxFloat64
|
||||
var f float64
|
||||
for quantile, epsilon := range targets {
|
||||
if quantile*s.n <= r {
|
||||
f = (2 * epsilon * r) / quantile
|
||||
} else {
|
||||
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
|
||||
}
|
||||
if f < m {
|
||||
m = f
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||
// design. Take care when using across multiple goroutines.
|
||||
type Stream struct {
|
||||
*stream
|
||||
b Samples
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newStream(ƒ invariant) *Stream {
|
||||
x := &stream{ƒ: ƒ}
|
||||
return &Stream{x, make(Samples, 0, 500), true}
|
||||
}
|
||||
|
||||
// Insert inserts v into the stream.
|
||||
func (s *Stream) Insert(v float64) {
|
||||
s.insert(Sample{Value: v, Width: 1})
|
||||
}
|
||||
|
||||
func (s *Stream) insert(sample Sample) {
|
||||
s.b = append(s.b, sample)
|
||||
s.sorted = false
|
||||
if len(s.b) == cap(s.b) {
|
||||
s.flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns the computed qth percentiles value. If s was created with
|
||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||
// will return an unspecified result.
|
||||
func (s *Stream) Query(q float64) float64 {
|
||||
if !s.flushed() {
|
||||
// Fast path when there hasn't been enough data for a flush;
|
||||
// this also yields better accuracy for small sets of data.
|
||||
l := len(s.b)
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
i := int(float64(l) * q)
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
}
|
||||
s.maybeSort()
|
||||
return s.b[i].Value
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.query(q)
|
||||
}
|
||||
|
||||
// Merge merges samples into the underlying streams samples. This is handy when
|
||||
// merging multiple streams from separate threads, database shards, etc.
|
||||
//
|
||||
// ATTENTION: This method is broken and does not yield correct results. The
|
||||
// underlying algorithm is not capable of merging streams correctly.
|
||||
func (s *Stream) Merge(samples Samples) {
|
||||
sort.Sort(samples)
|
||||
s.stream.merge(samples)
|
||||
}
|
||||
|
||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||
func (s *Stream) Reset() {
|
||||
s.stream.reset()
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
// Samples returns stream samples held by s.
|
||||
func (s *Stream) Samples() Samples {
|
||||
if !s.flushed() {
|
||||
return s.b
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.samples()
|
||||
}
|
||||
|
||||
// Count returns the total number of samples observed in the stream
|
||||
// since initialization.
|
||||
func (s *Stream) Count() int {
|
||||
return len(s.b) + s.stream.count()
|
||||
}
|
||||
|
||||
func (s *Stream) flush() {
|
||||
s.maybeSort()
|
||||
s.stream.merge(s.b)
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
func (s *Stream) maybeSort() {
|
||||
if !s.sorted {
|
||||
s.sorted = true
|
||||
sort.Sort(s.b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) flushed() bool {
|
||||
return len(s.stream.l) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
n float64
|
||||
l []Sample
|
||||
ƒ invariant
|
||||
}
|
||||
|
||||
func (s *stream) reset() {
|
||||
s.l = s.l[:0]
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
func (s *stream) insert(v float64) {
|
||||
s.merge(Samples{{v, 1, 0}})
|
||||
}
|
||||
|
||||
func (s *stream) merge(samples Samples) {
|
||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||
// whole summaries. The paper doesn't mention merging summaries at
|
||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||
// do merges properly.
|
||||
var r float64
|
||||
i := 0
|
||||
for _, sample := range samples {
|
||||
for ; i < len(s.l); i++ {
|
||||
c := s.l[i]
|
||||
if c.Value > sample.Value {
|
||||
// Insert at position i.
|
||||
s.l = append(s.l, Sample{})
|
||||
copy(s.l[i+1:], s.l[i:])
|
||||
s.l[i] = Sample{
|
||||
sample.Value,
|
||||
sample.Width,
|
||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||
// TODO(beorn7): How to calculate delta correctly?
|
||||
}
|
||||
i++
|
||||
goto inserted
|
||||
}
|
||||
r += c.Width
|
||||
}
|
||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||
i++
|
||||
inserted:
|
||||
s.n += sample.Width
|
||||
r += sample.Width
|
||||
}
|
||||
s.compress()
|
||||
}
|
||||
|
||||
func (s *stream) count() int {
|
||||
return int(s.n)
|
||||
}
|
||||
|
||||
func (s *stream) query(q float64) float64 {
|
||||
t := math.Ceil(q * s.n)
|
||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||
p := s.l[0]
|
||||
var r float64
|
||||
for _, c := range s.l[1:] {
|
||||
r += p.Width
|
||||
if r+c.Width+c.Delta > t {
|
||||
return p.Value
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.Value
|
||||
}
|
||||
|
||||
func (s *stream) compress() {
|
||||
if len(s.l) < 2 {
|
||||
return
|
||||
}
|
||||
x := s.l[len(s.l)-1]
|
||||
xi := len(s.l) - 1
|
||||
r := s.n - 1 - x.Width
|
||||
|
||||
for i := len(s.l) - 2; i >= 0; i-- {
|
||||
c := s.l[i]
|
||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||
x.Width += c.Width
|
||||
s.l[xi] = x
|
||||
// Remove element at i.
|
||||
copy(s.l[i:], s.l[i+1:])
|
||||
s.l = s.l[:len(s.l)-1]
|
||||
xi -= 1
|
||||
} else {
|
||||
x = c
|
||||
xi = i
|
||||
}
|
||||
r -= c.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) samples() Samples {
|
||||
samples := make(Samples, len(s.l))
|
||||
copy(samples, s.l)
|
||||
return samples
|
||||
}
|
31
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
31
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
|
@ -1,31 +0,0 @@
|
|||
Go support for Protocol Buffers - Google's data interchange format
|
||||
|
||||
Copyright 2010 The Go Authors. All rights reserved.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
43
vendor/github.com/golang/protobuf/proto/Makefile
generated
vendored
43
vendor/github.com/golang/protobuf/proto/Makefile
generated
vendored
|
@ -1,43 +0,0 @@
|
|||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
install:
|
||||
go install
|
||||
|
||||
test: install generate-test-pbs
|
||||
go test
|
||||
|
||||
|
||||
generate-test-pbs:
|
||||
make install
|
||||
make -C testdata
|
||||
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
|
||||
make
|
223
vendor/github.com/golang/protobuf/proto/clone.go
generated
vendored
223
vendor/github.com/golang/protobuf/proto/clone.go
generated
vendored
|
@ -1,223 +0,0 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Protocol buffer deep copy and merge.
|
||||
// TODO: RawMessage.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Clone returns a deep copy of a protocol buffer.
|
||||
func Clone(pb Message) Message {
|
||||
in := reflect.ValueOf(pb)
|
||||
if in.IsNil() {
|
||||
return pb
|
||||
}
|
||||
|
||||
out := reflect.New(in.Type().Elem())
|
||||
// out is empty so a merge is a deep copy.
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
return out.Interface().(Message)
|
||||
}
|
||||
|
||||
// Merge merges src into dst.
|
||||
// Required and optional fields that are set in src will be set to that value in dst.
|
||||
// Elements of repeated fields will be appended.
|
||||
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||
func Merge(dst, src Message) {
|
||||
in := reflect.ValueOf(src)
|
||||
out := reflect.ValueOf(dst)
|
||||
if out.IsNil() {
|
||||
panic("proto: nil destination")
|
||||
}
|
||||
if in.Type() != out.Type() {
|
||||
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||
panic("proto: type mismatch")
|
||||
}
|
||||
if in.IsNil() {
|
||||
// Merging nil into non-nil is a quiet no-op
|
||||
return
|
||||
}
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
}
|
||||
|
||||
func mergeStruct(out, in reflect.Value) {
|
||||
sprop := GetProperties(in.Type())
|
||||
for i := 0; i < in.NumField(); i++ {
|
||||
f := in.Type().Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
|
||||
}
|
||||
|
||||
if emIn, ok := in.Addr().Interface().(extendableProto); ok {
|
||||
emOut := out.Addr().Interface().(extendableProto)
|
||||
mergeExtension(emOut.ExtensionMap(), emIn.ExtensionMap())
|
||||
}
|
||||
|
||||
uf := in.FieldByName("XXX_unrecognized")
|
||||
if !uf.IsValid() {
|
||||
return
|
||||
}
|
||||
uin := uf.Bytes()
|
||||
if len(uin) > 0 {
|
||||
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
|
||||
}
|
||||
}
|
||||
|
||||
// mergeAny performs a merge between two values of the same type.
|
||||
// viaPtr indicates whether the values were indirected through a pointer (implying proto2).
|
||||
// prop is set if this is a struct field (it may be nil).
|
||||
func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) {
|
||||
if in.Type() == protoMessageType {
|
||||
if !in.IsNil() {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
|
||||
} else {
|
||||
Merge(out.Interface().(Message), in.Interface().(Message))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
if !viaPtr && isProto3Zero(in) {
|
||||
return
|
||||
}
|
||||
out.Set(in)
|
||||
case reflect.Interface:
|
||||
// Probably a oneof field; copy non-nil values.
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
// Allocate destination if it is not set, or set to a different type.
|
||||
// Otherwise we will merge as normal.
|
||||
if out.IsNil() || out.Elem().Type() != in.Elem().Type() {
|
||||
out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T)
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), false, nil)
|
||||
case reflect.Map:
|
||||
if in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(in.Type()))
|
||||
}
|
||||
// For maps with value types of *T or []byte we need to deep copy each value.
|
||||
elemKind := in.Type().Elem().Kind()
|
||||
for _, key := range in.MapKeys() {
|
||||
var val reflect.Value
|
||||
switch elemKind {
|
||||
case reflect.Ptr:
|
||||
val = reflect.New(in.Type().Elem().Elem())
|
||||
mergeAny(val, in.MapIndex(key), false, nil)
|
||||
case reflect.Slice:
|
||||
val = in.MapIndex(key)
|
||||
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||
default:
|
||||
val = in.MapIndex(key)
|
||||
}
|
||||
out.SetMapIndex(key, val)
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(in.Elem().Type()))
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), true, nil)
|
||||
case reflect.Slice:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if in.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// []byte is a scalar bytes field, not a repeated field.
|
||||
|
||||
// Edge case: if this is in a proto3 message, a zero length
|
||||
// bytes field is considered the zero value, and should not
|
||||
// be merged.
|
||||
if prop != nil && prop.proto3 && in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Make a deep copy.
|
||||
// Append to []byte{} instead of []byte(nil) so that we never end up
|
||||
// with a nil result.
|
||||
out.SetBytes(append([]byte{}, in.Bytes()...))
|
||||
return
|
||||
}
|
||||
n := in.Len()
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeSlice(in.Type(), 0, n))
|
||||
}
|
||||
switch in.Type().Elem().Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
out.Set(reflect.AppendSlice(out, in))
|
||||
default:
|
||||
for i := 0; i < n; i++ {
|
||||
x := reflect.Indirect(reflect.New(in.Type().Elem()))
|
||||
mergeAny(x, in.Index(i), false, nil)
|
||||
out.Set(reflect.Append(out, x))
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
mergeStruct(out, in)
|
||||
default:
|
||||
// unknown type, so not a protocol buffer
|
||||
log.Printf("proto: don't know how to copy %v", in)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeExtension(out, in map[int32]Extension) {
|
||||
for extNum, eIn := range in {
|
||||
eOut := Extension{desc: eIn.desc}
|
||||
if eIn.value != nil {
|
||||
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
|
||||
mergeAny(v, reflect.ValueOf(eIn.value), false, nil)
|
||||
eOut.value = v.Interface()
|
||||
}
|
||||
if eIn.enc != nil {
|
||||
eOut.enc = make([]byte, len(eIn.enc))
|
||||
copy(eOut.enc, eIn.enc)
|
||||
}
|
||||
|
||||
out[extNum] = eOut
|
||||
}
|
||||
}
|
868
vendor/github.com/golang/protobuf/proto/decode.go
generated
vendored
868
vendor/github.com/golang/protobuf/proto/decode.go
generated
vendored
|
@ -1,868 +0,0 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Routines for decoding protocol buffer data to construct in-memory representations.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// errOverflow is returned when an integer is too large to be represented.
|
||||
var errOverflow = errors.New("proto: integer overflow")
|
||||
|
||||
// ErrInternalBadWireType is returned by generated code when an incorrect
|
||||
// wire type is encountered. It does not get returned to user code.
|
||||
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||
|
||||
// The fundamental decoders that interpret bytes on the wire.
|
||||
// Those that take integer types all return uint64 and are
|
||||
// therefore of type valueDecoder.
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||
// It returns the integer and the number of bytes consumed, or
|
||||
// zero if there is not enough.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||
// x, n already 0
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if n >= len(buf) {
|
||||
return 0, 0
|
||||
}
|
||||
b := uint64(buf[n])
|
||||
n++
|
||||
x |= (b & 0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return x, n
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the Buffer.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
|
||||
i := p.index
|
||||
l := len(p.buf)
|
||||
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if i >= l {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
b := p.buf[i]
|
||||
i++
|
||||
x |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
p.index = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
err = errOverflow
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFixed64 reads a 64-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed64, sfixed64, and double protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 8
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-8])
|
||||
x |= uint64(p.buf[i-7]) << 8
|
||||
x |= uint64(p.buf[i-6]) << 16
|
||||
x |= uint64(p.buf[i-5]) << 24
|
||||
x |= uint64(p.buf[i-4]) << 32
|
||||
x |= uint64(p.buf[i-3]) << 40
|
||||
x |= uint64(p.buf[i-2]) << 48
|
||||
x |= uint64(p.buf[i-1]) << 56
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFixed32 reads a 32-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed32, sfixed32, and float protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 4
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-4])
|
||||
x |= uint64(p.buf[i-3]) << 8
|
||||
x |= uint64(p.buf[i-2]) << 16
|
||||
x |= uint64(p.buf[i-1]) << 24
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint64 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint32 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
|
||||
return
|
||||
}
|
||||
|
||||
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||
// bytes, embedded messages
|
||||
|
||||
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||
// This is the format used for the bytes protocol buffer
|
||||
// type and for embedded messages.
|
||||
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
|
||||
n, err := p.DecodeVarint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nb := int(n)
|
||||
if nb < 0 {
|
||||
return nil, fmt.Errorf("proto: bad byte length %d", nb)
|
||||
}
|
||||
end := p.index + nb
|
||||
if end < p.index || end > len(p.buf) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
if !alloc {
|
||||
// todo: check if can get more uses of alloc=false
|
||||
buf = p.buf[p.index:end]
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
buf = make([]byte, nb)
|
||||
copy(buf, p.buf[p.index:])
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeStringBytes reads an encoded string from the Buffer.
|
||||
// This is the format used for the proto2 string type.
|
||||
func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
||||
buf, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||
oi := o.index
|
||||
|
||||
err := o.skip(t, tag, wire)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !unrecField.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ptr := structPointer_Bytes(base, unrecField)
|
||||
|
||||
// Add the skipped field to struct field
|
||||
obuf := o.buf
|
||||
|
||||
o.buf = *ptr
|
||||
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||
|
||||
o.buf = obuf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||
|
||||
var u uint64
|
||||
var err error
|
||||
|
||||
switch wire {
|
||||
case WireVarint:
|
||||
_, err = o.DecodeVarint()
|
||||
case WireFixed64:
|
||||
_, err = o.DecodeFixed64()
|
||||
case WireBytes:
|
||||
_, err = o.DecodeRawBytes(false)
|
||||
case WireFixed32:
|
||||
_, err = o.DecodeFixed32()
|
||||
case WireStartGroup:
|
||||
for {
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fwire := int(u & 0x7)
|
||||
if fwire == WireEndGroup {
|
||||
break
|
||||
}
|
||||
ftag := int(u >> 3)
|
||||
err = o.skip(t, ftag, fwire)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface representing objects that can
|
||||
// unmarshal themselves. The method should reset the receiver before
|
||||
// decoding starts. The argument points to data that may be
|
||||
// overwritten, so implementations should not keep references to the
|
||||
// buffer.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||
// decoded result in pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// Unmarshal resets pb before starting to unmarshal, so any
|
||||
// existing data in pb is always removed. Use UnmarshalMerge
|
||||
// to preserve and append to existing data.
|
||||
func Unmarshal(buf []byte, pb Message) error {
|
||||
pb.Reset()
|
||||
return UnmarshalMerge(buf, pb)
|
||||
}
|
||||
|
||||
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||
// writes the decoded result to pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// UnmarshalMerge merges into existing data in pb.
|
||||
// Most code should use Unmarshal instead.
|
||||
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
return u.Unmarshal(buf)
|
||||
}
|
||||
return NewBuffer(buf).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeMessage reads a count-delimited message from the Buffer.
|
||||
func (p *Buffer) DecodeMessage(pb Message) error {
|
||||
enc, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return NewBuffer(enc).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeGroup reads a tag-delimited group from the Buffer.
|
||||
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in the
|
||||
// Buffer and places the decoded result in pb. If the struct
|
||||
// underlying pb does not match the data in the buffer, the results can be
|
||||
// unpredictable.
|
||||
func (p *Buffer) Unmarshal(pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
err := u.Unmarshal(p.buf[p.index:])
|
||||
p.index = len(p.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||
|
||||
if collectStats {
|
||||
stats.Decode++
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshalType does the work of unmarshaling a structure.
|
||||
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||
var state errorState
|
||||
required, reqFields := prop.reqCount, uint64(0)
|
||||
|
||||
var err error
|
||||
for err == nil && o.index < len(o.buf) {
|
||||
oi := o.index
|
||||
var u uint64
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
wire := int(u & 0x7)
|
||||
if wire == WireEndGroup {
|
||||
if is_group {
|
||||
return nil // input is satisfied
|
||||
}
|
||||
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||
}
|
||||
tag := int(u >> 3)
|
||||
if tag <= 0 {
|
||||
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||
}
|
||||
fieldnum, ok := prop.decoderTags.get(tag)
|
||||
if !ok {
|
||||
// Maybe it's an extension?
|
||||
if prop.extendable {
|
||||
if e := structPointer_Interface(base, st).(extendableProto); isExtensionField(e, int32(tag)) {
|
||||
if err = o.skip(st, tag, wire); err == nil {
|
||||
ext := e.ExtensionMap()[int32(tag)] // may be missing
|
||||
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||
e.ExtensionMap()[int32(tag)] = ext
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Maybe it's a oneof?
|
||||
if prop.oneofUnmarshaler != nil {
|
||||
m := structPointer_Interface(base, st).(Message)
|
||||
// First return value indicates whether tag is a oneof field.
|
||||
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
|
||||
if err == ErrInternalBadWireType {
|
||||
// Map the error to something more descriptive.
|
||||
// Do the formatting here to save generated code space.
|
||||
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||
continue
|
||||
}
|
||||
p := prop.Prop[fieldnum]
|
||||
|
||||
if p.dec == nil {
|
||||
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||
continue
|
||||
}
|
||||
dec := p.dec
|
||||
if wire != WireStartGroup && wire != p.WireType {
|
||||
if wire == WireBytes && p.packedDec != nil {
|
||||
// a packable field
|
||||
dec = p.packedDec
|
||||
} else {
|
||||
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
decErr := dec(o, p, base)
|
||||
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||
err = decErr
|
||||
}
|
||||
if err == nil && p.Required {
|
||||
// Successfully decoded a required field.
|
||||
if tag <= 64 {
|
||||
// use bitmap for fields 1-64 to catch field reuse.
|
||||
var mask uint64 = 1 << uint64(tag-1)
|
||||
if reqFields&mask == 0 {
|
||||
// new required field
|
||||
reqFields |= mask
|
||||
required--
|
||||
}
|
||||
} else {
|
||||
// This is imprecise. It can be fooled by a required field
|
||||
// with a tag > 64 that is encoded twice; that's very rare.
|
||||
// A fully correct implementation would require allocating
|
||||
// a data structure, which we would like to avoid.
|
||||
required--
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if is_group {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if state.err != nil {
|
||||
return state.err
|
||||
}
|
||||
if required > 0 {
|
||||
// Not enough information to determine the exact field. If we use extra
|
||||
// CPU, we could determine the field only if the missing required field
|
||||
// has a tag <= 64 and we check reqFields.
|
||||
return &RequiredNotSetError{"{Unknown}"}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Individual type decoders
|
||||
// For each,
|
||||
// u is the decoded value,
|
||||
// v is a pointer to the field (pointer) in the struct
|
||||
|
||||
// Sizes of the pools to allocate inside the Buffer.
|
||||
// The goal is modest amortization and allocation
|
||||
// on at least 16-byte boundaries.
|
||||
const (
|
||||
boolPoolSize = 16
|
||||
uint32PoolSize = 8
|
||||
uint64PoolSize = 4
|
||||
)
|
||||
|
||||
// Decode a bool.
|
||||
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.bools) == 0 {
|
||||
o.bools = make([]bool, boolPoolSize)
|
||||
}
|
||||
o.bools[0] = u != 0
|
||||
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||
o.bools = o.bools[1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_BoolVal(base, p.field) = u != 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int32.
|
||||
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int64.
|
||||
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a string.
|
||||
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_String(base, p.field) = &s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_StringVal(base, p.field) = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bytes ([]byte).
|
||||
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_Bytes(base, p.field) = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool).
|
||||
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
*v = append(*v, u != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded bools
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
|
||||
y := *v
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
y = append(y, u != 0)
|
||||
}
|
||||
|
||||
*v = y
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32).
|
||||
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word32Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int32s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(uint32(u))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64).
|
||||
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
structPointer_Word64Slice(base, p.field).Append(u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word64Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int64s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of strings ([]string).
|
||||
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_StringSlice(base, p.field)
|
||||
*v = append(*v, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of slice of bytes ([][]byte).
|
||||
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BytesSlice(base, p.field)
|
||||
*v = append(*v, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a map field.
|
||||
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oi := o.index // index at the end of this map entry
|
||||
o.index -= len(raw) // move buffer back to start of map entry
|
||||
|
||||
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
|
||||
if mptr.Elem().IsNil() {
|
||||
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||
}
|
||||
v := mptr.Elem() // map[K]V
|
||||
|
||||
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||
// See enc_new_map for why.
|
||||
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||
|
||||
var valbase structPointer
|
||||
var valptr reflect.Value
|
||||
switch p.mtype.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
// []byte
|
||||
var dummy []byte
|
||||
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||
valbase = toStructPointer(valptr) // *[]byte
|
||||
case reflect.Ptr:
|
||||
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||
valbase = toStructPointer(valptr)
|
||||
default:
|
||||
// everything else
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valbase = toStructPointer(valptr.Addr()) // **V
|
||||
}
|
||||
|
||||
// Decode.
|
||||
// This parses a restricted wire format, namely the encoding of a message
|
||||
// with two fields. See enc_new_map for the format.
|
||||
for o.index < oi {
|
||||
// tagcode for key and value properties are always a single byte
|
||||
// because they have tags 1 and 2.
|
||||
tagcode := o.buf[o.index]
|
||||
o.index++
|
||||
switch tagcode {
|
||||
case p.mkeyprop.tagcode[0]:
|
||||
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||
return err
|
||||
}
|
||||
case p.mvalprop.tagcode[0]:
|
||||
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// TODO: Should we silently skip this instead?
|
||||
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||
}
|
||||
}
|
||||
keyelem, valelem := keyptr.Elem(), valptr.Elem()
|
||||
if !keyelem.IsValid() {
|
||||
keyelem = reflect.Zero(p.mtype.Key())
|
||||
}
|
||||
if !valelem.IsValid() {
|
||||
valelem = reflect.Zero(p.mtype.Elem())
|
||||
}
|
||||
|
||||
v.SetMapIndex(keyelem, valelem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a group.
|
||||
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||
}
|
||||
|
||||
// Decode an embedded message.
|
||||
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||
raw, e := o.DecodeRawBytes(false)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := structPointer_Interface(bas, p.stype)
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode a slice of embedded messages.
|
||||
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, false, base)
|
||||
}
|
||||
|
||||
// Decode a slice of embedded groups.
|
||||
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, true, base)
|
||||
}
|
||||
|
||||
// Decode a slice of structs ([]*struct).
|
||||
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||
v := reflect.New(p.stype)
|
||||
bas := toStructPointer(v)
|
||||
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||
|
||||
if is_group {
|
||||
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := v.Interface()
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
1331
vendor/github.com/golang/protobuf/proto/encode.go
generated
vendored
1331
vendor/github.com/golang/protobuf/proto/encode.go
generated
vendored
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue