From facda16ef1eea4c32efb231a606363298e14dd2f Mon Sep 17 00:00:00 2001 From: Dom Date: Mon, 16 Mar 2015 19:26:41 +0100 Subject: [PATCH 1/4] added lua version... --- index.lua | 105 ++++++++++ modules/_url.lua | 443 +++++++++++++++++++++++++++++++++++++++++++ modules/boundary.lua | 15 ++ 3 files changed, 563 insertions(+) create mode 100644 index.lua create mode 100644 modules/_url.lua create mode 100644 modules/boundary.lua diff --git a/index.lua b/index.lua new file mode 100644 index 0000000..ea5c40e --- /dev/null +++ b/index.lua @@ -0,0 +1,105 @@ +local timer = require('timer') +local boundary = require('boundary') +local io = require('io') +local net = require('net') + + +local __pgk = "BOUNDARY MEMCACHED" +local client +local _previous = {} +local host = "127.0.0.1" +local port = 11211 +local pollInterval = 1000 + + +if (boundary.param ~= nil) then + pollInterval = boundary.param.pollInterval or pollInterval + host = boundary.param.host or host + port = boundary.param.port or port + source = (type(boundary.param.source) == 'string' and boundary.param.source:gsub('%s+', '') ~= '' and boundary.param.source) or + io.popen("uname -n"):read('*line') +end + + +function berror(err) + if err then print(string.format("%s ERROR: %s", __pgk, tostring(err))) return err end +end + + + +function diff(a, b) + if a == nil or b == nil then return 0 end + return math.max(a - b, 0) +end + + +-- accumulate a value and return the difference from the previous value +function accumulate(key, newValue) + local oldValue = _previous[key] or newValue + local difference = diff(newValue, oldValue) + _previous[key] = newValue + return difference +end + +-- init client +function init() + if client == nil then + client = net.createConnection(port, host, function (err) + if berror(err) then end + end) + end + client:on("error", function(err) + berror(err) + end) + client:on("data", function(data) + + local d = {} + for _, v in pairs(split(data, "\r\n")) do + local s = v:gsub("STAT ", "") + local t = {} + for w in s:gmatch("%S+") do + table.insert(t, w) + end + d[t[1]] = tonumber(t[2]) + end + + print(string.format('MEMCACHED_ALLOCATED %d %s', d.bytes / d.limit_maxbytes, source)) + print(string.format('MEMCACHED_CONNECTIONS %d %s', d.curr_connections, source)) + print(string.format('MEMCACHED_HITS %d %s', accumulate('get_hits', d.get_hits), source)) + print(string.format('MEMCACHED_MISSES %d %s', accumulate('get_misses', d.get_misses), source)) + print(string.format('MEMCACHED_ITEMS %d %s', d.curr_items, source)) + print(string.format('MEMCACHED_REQUESTS %d %s', accumulate('cmd_get', d.cmd_get) + accumulate('cmd_set', d.cmd_set), source)) + print(string.format('MEMCACHED_NETWORK_IN %d %s', accumulate('bytes_read', d.bytes_read), source)) + print(string.format('MEMCACHED_NETWORK_OUT %d %s', accumulate('bytes_written', d.bytes_written), source)) + end) + +end + + +-- get the natural difference between a and b +function diff(a, b) + if not a or not b then return 0 end + return math.max(a - b, 0) +end + +function split(str, delim) + local res = {} + local pattern = string.format("([^%s]+)%s()", delim, delim) + while (true) do + line, pos = str:match(pattern, pos) + if line == nil then break end + table.insert(res, line) + end + return res +end + +print("_bevent:MEMCACHED plugin up : version 1.0|t:info|tags:memcached, lua, plugin") +init() + +timer.setInterval(pollInterval, function () + client:write("stats " .. "\r\n") +end) + + + + diff --git a/modules/_url.lua b/modules/_url.lua new file mode 100644 index 0000000..d417f07 --- /dev/null +++ b/modules/_url.lua @@ -0,0 +1,443 @@ +-- neturl.lua - a robust url parser and builder +-- +-- Bertrand Mansion, 2011-2013; License MIT +-- @module neturl +-- @alias M + +local M = {} +M.version = "0.9.0" + +--- url options +-- separator is set to `&` by default but could be anything like `&` or `;` +-- @todo Add an option to limit the size of the argument table +M.options = { + separator = '&' +} + +--- list of known and common scheme ports +-- as documented in IANA URI scheme list +M.services = { + acap = 674, + cap = 1026, + dict = 2628, + ftp = 21, + gopher = 70, + http = 80, + https = 443, + iax = 4569, + icap = 1344, + imap = 143, + ipp = 631, + ldap = 389, + mtqp = 1038, + mupdate = 3905, + news = 2009, + nfs = 2049, + nntp = 119, + rtsp = 554, + sip = 5060, + snmp = 161, + telnet = 23, + tftp = 69, + vemmi = 575, + afs = 1483, + jms = 5673, + rsync = 873, + prospero = 191, + videotex = 516 +} + +local legal = { + ["-"] = true, ["_"] = true, ["."] = true, ["!"] = true, + ["~"] = true, ["*"] = true, ["'"] = true, ["("] = true, + [")"] = true, [":"] = true, ["@"] = true, ["&"] = true, + ["="] = true, ["+"] = true, ["$"] = true, [","] = true, + [";"] = true -- can be used for parameters in path +} + +local function decode(str) + local str = str:gsub('+', ' ') + return (str:gsub("%%(%x%x)", function(c) + return string.char(tonumber(c, 16)) + end)) +end + +local function encode(str) + return (str:gsub("([^A-Za-z0-9%_%.%-%~])", function(v) + return string.upper(string.format("%%%02x", string.byte(v))) + end)) +end + +-- for query values, prefer + instead of %20 for spaces +local function encodeValue(str) + local str = encode(str) + return str:gsub('%%20', '+') +end + +local function encodeSegment(s) + local legalEncode = function(c) + if legal[c] then + return c + end + return encode(c) + end + return s:gsub('([^a-zA-Z0-9])', legalEncode) +end + +--- builds the url +-- @return a string representing the built url +function M:build() + local url = '' + if self.path then + local path = self.path + path:gsub("([^/]+)", function (s) return encodeSegment(s) end) + url = url .. tostring(path) + end + if self.query then + local qstring = tostring(self.query) + if qstring ~= "" then + url = url .. '?' .. qstring + end + end + if self.host then + local authority = self.host + if self.port and self.scheme and M.services[self.scheme] ~= self.port then + authority = authority .. ':' .. self.port + end + local userinfo + if self.user and self.user ~= "" then + userinfo = self.user + if self.password then + userinfo = userinfo .. ':' .. self.password + end + end + if userinfo and userinfo ~= "" then + authority = userinfo .. '@' .. authority + end + if authority then + if url ~= "" then + url = '//' .. authority .. '/' .. url:gsub('^/+', '') + else + url = '//' .. authority + end + end + end + if self.scheme then + url = self.scheme .. ':' .. url + end + if self.fragment then + url = url .. '#' .. self.fragment + end + return url +end + +--- builds the querystring +-- @param tab The key/value parameters +-- @param sep The separator to use (optional) +-- @param key The parent key if the value is multi-dimensional (optional) +-- @return a string representing the built querystring +function M.buildQuery(tab, sep, key) + local query = {} + if not sep then + sep = M.options.separator or '&' + end + local keys = {} + for k in pairs(tab) do + keys[#keys+1] = k + end + table.sort(keys) + for _,name in ipairs(keys) do + local value = tab[name] + name = encode(tostring(name)) + if key then + name = string.format('%s[%s]', tostring(key), tostring(name)) + end + if type(value) == 'table' then + query[#query+1] = M.buildQuery(value, sep, name) + else + local value = encodeValue(tostring(value)) + if value ~= "" then + query[#query+1] = string.format('%s=%s', name, value) + else + query[#query+1] = name + end + end + end + return table.concat(query, sep) +end + +--- Parses the querystring to a table +-- This function can parse multidimensional pairs and is mostly compatible +-- with PHP usage of brackets in key names like ?param[key]=value +-- @param str The querystring to parse +-- @param sep The separator between key/value pairs, defaults to `&` +-- @todo limit the max number of parameters with M.options.max_parameters +-- @return a table representing the query key/value pairs +function M.parseQuery(str, sep) + if not sep then + sep = M.options.separator or '&' + end + + local values = {} + for key,val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do + local key = decode(key) + local keys = {} + key = key:gsub('%[([^%]]*)%]', function(v) + -- extract keys between balanced brackets + if string.find(v, "^-?%d+$") then + v = tonumber(v) + else + v = decode(v) + end + table.insert(keys, v) + return "=" + end) + key = key:gsub('=+.*$', "") + key = key:gsub('%s', "_") -- remove spaces in parameter name + val = val:gsub('^=+', "") + + if not values[key] then + values[key] = {} + end + if #keys > 0 and type(values[key]) ~= 'table' then + values[key] = {} + elseif #keys == 0 and type(values[key]) == 'table' then + values[key] = decode(val) + end + + local t = values[key] + for i,k in ipairs(keys) do + if type(t) ~= 'table' then + t = {} + end + if k == "" then + k = #t+1 + end + if not t[k] then + t[k] = {} + end + if i == #keys then + t[k] = decode(val) + end + t = t[k] + end + end + setmetatable(values, { __tostring = M.buildQuery }) + return values +end + +--- set the url query +-- @param query Can be a string to parse or a table of key/value pairs +-- @return a table representing the query key/value pairs +function M:setQuery(query) + local query = query + if type(query) == 'table' then + query = M.buildQuery(query) + end + self.query = M.parseQuery(query) + return query +end + +--- set the authority part of the url +-- The authority is parsed to find the user, password, port and host if available. +-- @param authority The string representing the authority +-- @return a string with what remains after the authority was parsed +function M:setAuthority(authority) + self.authority = authority + self.port = nil + self.host = nil + self.userinfo = nil + self.user = nil + self.password = nil + + authority = authority:gsub('^([^@]*)@', function(v) + self.userinfo = v + return '' + end) + authority = authority:gsub("^%[[^%]]+%]", function(v) + -- ipv6 + self.host = v + return '' + end) + authority = authority:gsub(':([^:]*)$', function(v) + self.port = tonumber(v) + return '' + end) + if authority ~= '' and not self.host then + self.host = authority:lower() + end + if self.userinfo then + local userinfo = self.userinfo + userinfo = userinfo:gsub(':([^:]*)$', function(v) + self.password = v + return '' + end) + self.user = userinfo + end + return authority +end + +--- Parse the url into the designated parts. +-- Depending on the url, the following parts can be available: +-- scheme, userinfo, user, password, authority, host, port, path, +-- query, fragment +-- @param url Url string +-- @return a table with the different parts and a few other functions +function M.parse(url) + local comp = {} + M.setAuthority(comp, "") + M.setQuery(comp, "") + + local url = tostring(url or '') + url = url:gsub('#(.*)$', function(v) + comp.fragment = v + return '' + end) + url =url:gsub('^([%w][%w%+%-%.]*)%:', function(v) + comp.scheme = v:lower() + return '' + end) + url = url:gsub('%?(.*)', function(v) + M.setQuery(comp, v) + return '' + end) + url = url:gsub('^//([^/]*)', function(v) + M.setAuthority(comp, v) + return '' + end) + comp.path = decode(url) + + setmetatable(comp, { + __index = M, + __tostring = M.build} + ) + return comp +end + +--- removes dots and slashes in urls when possible +-- This function will also remove multiple slashes +-- @param path The string representing the path to clean +-- @return a string of the path without unnecessary dots and segments +function M.removeDotSegments(path) + local fields = {} + if string.len(path) == 0 then + return "" + end + local startslash = false + local endslash = false + if string.sub(path, 1, 1) == "/" then + startslash = true + end + if (string.len(path) > 1 or startslash == false) and string.sub(path, -1) == "/" then + endslash = true + end + + path:gsub('[^/]+', function(c) table.insert(fields, c) end) + + local new = {} + local j = 0 + + for i,c in ipairs(fields) do + if c == '..' then + if j > 0 then + j = j - 1 + end + elseif c ~= "." then + j = j + 1 + new[j] = c + end + end + local ret = "" + if #new > 0 and j > 0 then + ret = table.concat(new, '/', 1, j) + else + ret = "" + end + if startslash then + ret = '/'..ret + end + if endslash then + ret = ret..'/' + end + return ret +end + +local function absolutePath(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then + return '/' .. string.gsub(relative_path, '^[%./]+', '') + end + local path = base_path + if relative_path ~= "" then + path = '/'..path:gsub("[^/]*$", "") + end + path = path .. relative_path + path = path:gsub("([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end + end) + end + path = string.gsub(path, "([^/]*/%.%.?)$", function (s) + if s ~= "../.." then return "" else return s end + end) + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, '^/?%.%./', '') + end + return '/' .. path +end + +--- builds a new url by using the one given as parameter and resolving paths +-- @param other A string or a table representing a url +-- @return a new url table +function M:resolve(other) + if type(self) == "string" then + self = M.parse(self) + end + if type(other) == "string" then + other = M.parse(other) + end + if other.scheme then + return other + else + other.scheme = self.scheme + if not other.authority or other.authority == "" then + other:setAuthority(self.authority) + if not other.path or other.path == "" then + other.path = self.path + local query = other.query + if not query or not next(query) then + other.query = self.query + end + else + other.path = absolutePath(self.path, other.path) + end + end + return other + end +end + +--- normalize a url path following some common normalization rules +-- described on The URL normalization page of Wikipedia +-- @return the normalized path +function M:normalize() + if type(self) == 'string' then + self = M.parse(self) + end + if self.path then + local path = self.path + path = absolutePath(path, "") + -- normalize multiple slashes + path = string.gsub(path, "//+", "/") + self.path = path + end + return self +end + +return M \ No newline at end of file diff --git a/modules/boundary.lua b/modules/boundary.lua new file mode 100644 index 0000000..e1bcb43 --- /dev/null +++ b/modules/boundary.lua @@ -0,0 +1,15 @@ +-- Copyright 2015 Boundary +-- @brief convenience variables and functions for Lua scripts +-- @file boundary.lua +local fs = require('fs') +local json = require('json') + +local boundary = {param = nil} + +-- import param.json data into a Lua table (boundary.param) +local json_blob +if (pcall(function () json_blob = fs.readFileSync("param.json") end)) then + pcall(function () boundary.param = json.parse(json_blob) end) +end + +return boundary From 65ac2bedaaf48efd3b50dab91e7d7c788b8a44d8 Mon Sep 17 00:00:00 2001 From: TheBigHNF Date: Wed, 18 Mar 2015 14:40:20 -0500 Subject: [PATCH 2/4] Standard fixes to plugins Move index.la to init.lua Added command_lua/postInstall.lua to plugin.json Used README.md template --- README.md | 27 ++++++++++++--- index.lua => init.lua | 0 modules/boundary.lua | 15 --------- plugin.json | 78 ++++++++++++++++++++++++++----------------- 4 files changed, 71 insertions(+), 49 deletions(-) rename index.lua => init.lua (100%) delete mode 100644 modules/boundary.lua diff --git a/README.md b/README.md index 2fa67ea..50bb723 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,28 @@ -Boundary Memcached Plugin -------------------------- +# Boundary Memcached Plugin Collects metrics from a memcached instance. See video [walkthrough](https://help.boundary.com/hc/articles/201816101). -### Prerequisites +## Prerequisites + +### Supported OS | OS | Linux | Windows | SmartOS | OS X | |:----------|:-----:|:-------:|:-------:|:----:| | Supported | v | v | v | v | +#### Boundary Meter Versions V4.0 Or Greater + +To get the new meter: + + curl -fsS \ + -d "{\"token\":\"\"}" \ + -H "Content-Type: application/json" \ + "https://meter.boundary.com/setup_meter" > setup_meter.sh + chmod +x setup_meter.sh + ./setup_meter.sh + +#### For Boundary Meter less than V4.0 + | Runtime | node.js | Python | Java | |:---------|:-------:|:------:|:----:| | Required | + | | | @@ -18,7 +32,9 @@ Collects metrics from a memcached instance. See video [walkthrough](https://help ### Plugin Setup None -#### Plugin Configuration Fields +### Plugin Configuration Fields + +#### For All Versions |Field Name|Description | |:---------|:----------------------------------------------------------| @@ -27,6 +43,9 @@ None |Host |The MEMCACHED hostname. | ### Metrics Collected + +#### For All Versions + |Metric Name |Description | |:--------------------|:---------------------------------| |Memcached Allocated |Percent of available memory used | diff --git a/index.lua b/init.lua similarity index 100% rename from index.lua rename to init.lua diff --git a/modules/boundary.lua b/modules/boundary.lua deleted file mode 100644 index e1bcb43..0000000 --- a/modules/boundary.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright 2015 Boundary --- @brief convenience variables and functions for Lua scripts --- @file boundary.lua -local fs = require('fs') -local json = require('json') - -local boundary = {param = nil} - --- import param.json data into a Lua table (boundary.param) -local json_blob -if (pcall(function () json_blob = fs.readFileSync("param.json") end)) then - pcall(function () boundary.param = json.parse(json_blob) end) -end - -return boundary diff --git a/plugin.json b/plugin.json index f8a9684..5d7598e 100644 --- a/plugin.json +++ b/plugin.json @@ -1,31 +1,49 @@ { - "description" : "Displays important MEMCACHED metrics", - "icon" : "icon.png", - "command" : "node index.js", - "postExtract" : "npm install", - "ignore" : "node_modules", - "metrics" : ["MEMCACHED_ALLOCATED", "MEMCACHED_CONNECTIONS", "MEMCACHED_HITS", "MEMCACHED_MISSES", "MEMCACHED_ITEMS", - "MEMCACHED_REQUESTS", "MEMCACHED_NETWORK_IN", "MEMCACHED_NETWORK_OUT"], - "dashboards" : [{ "name" : "memcached", "layout" : "d-w=3&d-h=2&d-pad=5&d-bg=none&d-g-MEMCACHED_ALLOCATED=0-0-1-1&d-g-MEMCACHED_CONNECTIONS=1-0-1-1&d-g-MEMCACHED_HITS=0-1-1-1-t&d-g-MEMCACHED_MISSES=0-1-1-1-b&d-g-MEMCACHED_ITEMS=1-1-1-1&d-g-MEMCACHED_REQUESTS=2-0-1-1&d-g-MEMCACHED_NETWORK_IN=2-1-1-1-t&d-g-MEMCACHED_NETWORK_OUT=2-1-1-1-b"}], - "paramSchema" : [ - { - "title" : "Source", - "name" : "source", - "description" : "The source to display in the legend for the MEMCACHED data.", - "type" : "string" - }, - { - "title" : "Port", - "name" : "port", - "description" : "The MEMCACHED port.", - "type" : "integer", - "default" : 11211 - }, - { - "title" : "Host", - "name" : "host", - "description" : "The MEMCACHED hostname.", - "type" : "string" - } - ] -} \ No newline at end of file + "description" : "Displays important MEMCACHED metrics", + "icon" : "icon.png", + "command" : "node index.js", + "postExtract" : "npm install", + "command_lua" : "boundary-meter init.lua", + "postExtract_lua" : "", + "ignore" : "node_modules", + + "metrics" : [ + "MEMCACHED_ALLOCATED", + "MEMCACHED_CONNECTIONS", + "MEMCACHED_HITS", + "MEMCACHED_MISSES", + "MEMCACHED_ITEMS", + "MEMCACHED_REQUESTS", + "MEMCACHED_NETWORK_IN", + "MEMCACHED_NETWORK_OUT" + ], + + "dashboards" : [ + { + "name" : "memcached", + "layout" : "d-w=3&d-h=2&d-pad=5&d-bg=none&d-g-MEMCACHED_ALLOCATED=0-0-1-1&d-g-MEMCACHED_CONNECTIONS=1-0-1-1&d-g-MEMCACHED_HITS=0-1-1-1-t&d-g-MEMCACHED_MISSES=0-1-1-1-b&d-g-MEMCACHED_ITEMS=1-1-1-1&d-g-MEMCACHED_REQUESTS=2-0-1-1&d-g-MEMCACHED_NETWORK_IN=2-1-1-1-t&d-g-MEMCACHED_NETWORK_OUT=2-1-1-1-b" + } + ], + + "paramSchema" : [ + { + "title" : "Source", + "name" : "source", + "description" : "The source to display in the legend for the MEMCACHED data.", + "type" : "string" + }, + { + "title" : "Port", + "name" : "port", + "description" : "The MEMCACHED port.", + "type" : "integer", + "default" : 11211 + }, + { + "title" : "Host", + "name" : "host", + "description" : "The MEMCACHED hostname.", + "type" : "string" + } + ] +} From 8f6ec28381c8246a3ee0307b91abe3822f8d8283 Mon Sep 17 00:00:00 2001 From: TheBigHNF Date: Wed, 18 Mar 2015 15:12:02 -0500 Subject: [PATCH 3/4] Used README.md template --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 50bb723..96a65de 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ To get the new meter: - [How to install node.js?](https://help.boundary.com/hc/articles/202360701) ### Plugin Setup + None ### Plugin Configuration Fields From 5b5e81328981d3aaca68ca809639bf67b99feded Mon Sep 17 00:00:00 2001 From: TheBigHNF Date: Sun, 10 May 2015 22:07:57 -0500 Subject: [PATCH 4/4] Updated README --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 96a65de..221cc57 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,10 @@ Collects metrics from a memcached instance. See video [walkthrough](https://help |:----------|:-----:|:-------:|:-------:|:----:| | Supported | v | v | v | v | -#### Boundary Meter Versions V4.0 Or Greater +#### Boundary Meter Versions V4.0 Or Later -To get the new meter: - - curl -fsS \ - -d "{\"token\":\"\"}" \ - -H "Content-Type: application/json" \ - "https://meter.boundary.com/setup_meter" > setup_meter.sh - chmod +x setup_meter.sh - ./setup_meter.sh +- To install new meter go to Settings->Installation or [see instructons|https://help.boundary.com/hc/en-us/sections/200634331-Installation]. +- To upgrade the meter to the latest version - [see instructons|https://help.boundary.com/hc/en-us/articles/201573102-Upgrading-the-Boundary-Meter]. #### For Boundary Meter less than V4.0