One of the people using our nagios server asked the other day:
"I know this doesn't work, but can https://nagios.our.site/ourhost go directly to the page for host?"
I explained that the frames inside the current version of Nagios that we use were simply
links to cgi urls like
https://nagios.our.site/cgi-bin/status.cgi?type=1&host=ourhost
and that you could type that url into the browser and get a direct link. As I was explaining this I realized that it would not be too hard to use Apache Rewrite to create a url like this
https://nagios.our.site/host/ourhost
This is what I added to the nagios.conf file in /etc/httpd/conf.d to accomplish this.
RewriteEngine On
RewriteLog /etc/httpd/logs/ssl_rewrite_log
# Rewrite host/foo to basic cgi view url
RewriteRule ^/host/(.*\.cgi)$ /cgi-bin/$1 [L,PT]
RewriteRule ^/host/([a-z0-9\-]+*)$ /cgi-bin/status.cgi?host=$1 [L,PT]
The first rule rewrites any links clicked from with the initial page to correctly redirect to the appropriate CGI, the second rewrites the /host/ourhost to be a CGI url pointing to the basic status page. The options are important, the first L means "last rule, stop attempting to rewrite the url", PT means that it is not a plain file, but needs to go back through the URI engine to be resolved to a cgi.
It's a simple hack, but people seem to like it. Which is probably more a statement about the Nagios dashboard than the utility of this tweak.
Friday, November 14, 2014
Monday, September 15, 2014
Elixir command line parsing
I haven't had much luck finding a complete example of command line parsing in Elixir, so I thought I'd share what I've come up with so far.
This example shows uses a Map to ultimately store the options as key, value pairs. This implies that there can be only one value for any given option.
This function defines the main flow of control in the program.
The main command line argument parsing function. It sets up an option map with default values and then merges in any values from the command line. It also allows undefined options, see
A simple helper function to make the main parse routine less complicated.
The function
This is an example of using elixir's pattern matching in function definitions to allow it to route flow of control in the program. The value returned from the pargs_args call will determine which version of the process function gets run. This code in actual use can be found in my github repo https://github.com/bbense/elixgrep
This example shows uses a Map to ultimately store the options as key, value pairs. This implies that there can be only one value for any given option.
def main(args) do
args |> parse_args |> process
end
This function defines the main flow of control in the program.
def parse_args(args) do
options = %{ :count => @max_ofiles ,
:type => @default_type
}
cmd_opts = OptionParser.parse(args,
switches: [help: :boolean , count: :integer],
aliases: [h: :help, c: :count])
case cmd_opts do
{ [ help: true], _, _} -> :help
{ [], args, [] } -> { options, args }
{ opts, args, [] } -> { Enum.into(opts,options), args }
{ opts, args, bad_opts} -> {
Enum.into(merge_opts(opts,bad_opts),
options),
args
}
_ -> :help
end
end
The main command line argument parsing function. It sets up an option map with default values and then merges in any values from the command line. It also allows undefined options, see
rehabilitate_args
below.
def merge_opts(opts,bad_opts) do
bad_opts |> rehabilitate_args |> Keyword.merge(opts)
end
A simple helper function to make the main parse routine less complicated.
def rehabilitate_args(bad_args) do
bad_args
|>
Enum.flat_map(fn(x) -> Tuple.to_list(x) end)
|>
Enum.filter_map(fn(str) -> str end,
fn(str) ->
String.replace(str, ~r/^\-([^-]+)/, "--\\1")
end )
|>
OptionParser.parse
|>
Tuple.to_list
|>
List.first
end
The function
rehabilitate_args
is something I've included since my application will use plugins and I would like plugin authors to be able to simply use command line args w/o a complicated interface. This might or might not be a good idea and is mostly included as an example of how to handle bad_args if you want to. If you use :strict
rather than :switches
in the OptionParser.parse
call undefined options will automatically generate an error.
def process(:help) do
IO.puts @module_doc
System.halt(0)
end
def process({options,args}) do
AllTheRealWork.start
end
This is an example of using elixir's pattern matching in function definitions to allow it to route flow of control in the program. The value returned from the pargs_args call will determine which version of the process function gets run. This code in actual use can be found in my github repo https://github.com/bbense/elixgrep
Friday, September 12, 2014
Elixir spawn/1 and spawn/3
Since I spent an afternoon banging my head against this, I thought I'd blog it.
If you have elixir code that dies with this message
when you do this
But runs just fine when you do this
The problem is the arity of the function you are using in the spawn/3 version.
Elixir uses the count of the elements in the third argument to spawn/3 to
find the function to spawn in the subprocess. In the first version it is looking
for a
to
it will then run without the process error above.
If you have elixir code that dies with this message
17:39:25.661 [error] Error in process <0.60.0> with exit value: {undef,[{'Elixir.MyFunModule',fun_stuff,[],[]}]}
pid = spawn( MyFunModule, :fun_stuff, [] )
But runs just fine when you do this
pid = spawn( fn -> MyFunModule.funstuff([]) end )
The problem is the arity of the function you are using in the spawn/3 version.
Elixir uses the count of the elements in the third argument to spawn/3 to
find the function to spawn in the subprocess. In the first version it is looking
for a
MyFunModule.fun_stuff/0
which does not exist.
If you changepid = spawn( MyFunModule, :fun_stuff, [] )
to
pid = spawn( MyFunModule, :fun_stuff, [[]] )
it will then run without the process error above.
Thursday, August 21, 2014
Multi-Core, Languages and the Dreaded GIL
For a very long time[1], I have always thought that the only way to deal with the coming multi-core explosion is to move to a language that deals with concurrency as a core feature in the language. This is hardly a novel idea. It has always been obvious that threaded code in standard languages is next to impossible to write correctly. This implies that the only way to deal with threads is to hide them in an abstraction in which you can devote enough resources to ensure a reasonably robust implementation. Writing thread safe code is extremely difficult and requires a huge payback to justify the effort. It's exactly the same reasoning behind allowing the kernel to do memory and process management, rather than having every application write it's own OS.
In the face of this I have made a serious effort to learn Erlang, Go and dip my toe in functional languages in an attempt to be ready for adapting to this multi-core world. As much as I love Ruby,
it was obvious that it simply would not be a reasonable solution for a machine with 32 cores and above. It and many of the other "scripting" languages, depend on having a lock on the dreaded GIL[2]. This is generally not a problem for code that runs websites due to the fact that they are largely I/O bound[3]. However, for any other workload that requires CPU these languages would be relegated to the "fancy shell script" problem domain. You simply wouldn't write anything serious in these languages, since they can't effectively use 80-90% of the available resources on a high end server.
Recently, I have begun to rethink this assumption. The reality is that most 32 core machines are running VM's in one form or another. Docker[4], in particular, provides a highly efficient means of simulating a single core OS for a single core process. A common theme of the "multi-core" ready languages is to avoid the problems of threading by switching the model from shared memory to very fast messaging. If it is possible to create an interprocess message system between Docker images that can compete with speed of Go channels or Erlang messaging, then it should be possible for languages like Ruby to be a reasonable alternative in this brave new world.
There was an attempt to allow this kind of distributed processing in Ruby, DRb. It never really gained wide usage primarily due to the latency and AAA[5] issues. However, if there was an API that allowed communication between Docker instances that could be both fast and trusted, it would be possible to revive Drb and similar "remote object" implementations. I think this is the only workable way forward for the "interpreted" languages. Essentially you have to convert the message sending primitive to sending a message to remote[6] objects. This isn't as hard as it looks at first blush, Docker images are simply processes running in the same kernel on the same machine. They just have different ideas about their various interfaces to the rest of the world. It should be possible to "short-circuit" these interfaces to allow them to communicate at something close to the speeds available to more concurrency aware languages.
[ After a couple days of thinking about what this really means.]
This idea looks more like "sprinkle current fairy dust" on a hard problem, than an actual solution. I think what I was trying to wrap my brain around was the idea of implementing something like the old sun RPC interface via standard ports as a way to allow you to use Docker to avoid all the overhead of
doing message passing between ruby processes via pipes, etc...
I think there is something there, but it's far from fleshed out enough. Maybe just creating a lightweight micro services API and using Docker to allow those API's to talk on the same machine
might be enough. I am well aware of the first rule of Distributed Objects.
http://martinfowler.com/articles/distributed-objects-microservices.html
[1]- Since about 1997 or so (i.e. when I first attempted to write pthreaded code ).
[2]- Global Interpreter Lock https://en.wikipedia.org/wiki/Global_Interpreter_Lock
[3]- i.e. Turning database entries into web pages and web forms into database entries.
[4] - https://www.docker.com/
[5]- Authentication, Authorization and Accounting
[6]- If they are in the same memory accessed by the same cpu, they really aren't "remote"
In the face of this I have made a serious effort to learn Erlang, Go and dip my toe in functional languages in an attempt to be ready for adapting to this multi-core world. As much as I love Ruby,
it was obvious that it simply would not be a reasonable solution for a machine with 32 cores and above. It and many of the other "scripting" languages, depend on having a lock on the dreaded GIL[2]. This is generally not a problem for code that runs websites due to the fact that they are largely I/O bound[3]. However, for any other workload that requires CPU these languages would be relegated to the "fancy shell script" problem domain. You simply wouldn't write anything serious in these languages, since they can't effectively use 80-90% of the available resources on a high end server.
Recently, I have begun to rethink this assumption. The reality is that most 32 core machines are running VM's in one form or another. Docker[4], in particular, provides a highly efficient means of simulating a single core OS for a single core process. A common theme of the "multi-core" ready languages is to avoid the problems of threading by switching the model from shared memory to very fast messaging. If it is possible to create an interprocess message system between Docker images that can compete with speed of Go channels or Erlang messaging, then it should be possible for languages like Ruby to be a reasonable alternative in this brave new world.
There was an attempt to allow this kind of distributed processing in Ruby, DRb. It never really gained wide usage primarily due to the latency and AAA[5] issues. However, if there was an API that allowed communication between Docker instances that could be both fast and trusted, it would be possible to revive Drb and similar "remote object" implementations. I think this is the only workable way forward for the "interpreted" languages. Essentially you have to convert the message sending primitive to sending a message to remote[6] objects. This isn't as hard as it looks at first blush, Docker images are simply processes running in the same kernel on the same machine. They just have different ideas about their various interfaces to the rest of the world. It should be possible to "short-circuit" these interfaces to allow them to communicate at something close to the speeds available to more concurrency aware languages.
[ After a couple days of thinking about what this really means.]
This idea looks more like "sprinkle current fairy dust" on a hard problem, than an actual solution. I think what I was trying to wrap my brain around was the idea of implementing something like the old sun RPC interface via standard ports as a way to allow you to use Docker to avoid all the overhead of
doing message passing between ruby processes via pipes, etc...
I think there is something there, but it's far from fleshed out enough. Maybe just creating a lightweight micro services API and using Docker to allow those API's to talk on the same machine
might be enough. I am well aware of the first rule of Distributed Objects.
http://martinfowler.com/articles/distributed-objects-microservices.html
[1]- Since about 1997 or so (i.e. when I first attempted to write pthreaded code ).
[2]- Global Interpreter Lock https://en.wikipedia.org/wiki/Global_Interpreter_Lock
[3]- i.e. Turning database entries into web pages and web forms into database entries.
[4] - https://www.docker.com/
[5]- Authentication, Authorization and Accounting
[6]- If they are in the same memory accessed by the same cpu, they really aren't "remote"
Labels:
concurrency,
erlang,
golang,
multi-core,
ruby
Subscribe to:
Posts (Atom)