Playing with Go: Embarrassingly Parallel Scripts
collectiveidea.com
collectiveidea.com
package main
import (
"fmt"
"net"
"io/ioutil"
"strings"
)
func main() {
file_in, _ := ioutil.ReadFile("domains.txt")
domain_list := string(file_in)
done := make(chan bool)
count := 0
for _, domain := range strings.Split(strings.TrimSpace(domain_list), "\n") {
go func(d string) {
ipAddresses, _ := net.LookupIP(d)
ip := ""
if len(ipAddresses) > 0 {
ip = ipAddresses[0].String()
}
fmt.Println("Mapping: ", d, "->", ip)
done <- true
}(domain)
count++
}
for i := 0; i < count; i++ {
<- done
}
}Instead of counting and using a done channel,
import "sync"
...
var wg sync.WaitGroup
for ... {
wg.Add(1)
go dowork()
}
wg.Wait()Since the standard libs in Go duplicate code, you would have to be careful that your suggestion system isn't picking up false positives. I think the idea has a lot of promise though.
Something like this would work even better:
result = src.asyncMap { |e| dowork(e) };
Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is. And with no generics it would be annoying to actually use e and the result, since they would need casts. Too bad.> Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is.
This is false. Functions in Go do not have to return multiple values. Therefore, you can "collect all the results as-is".
> And with no generics it would be annoying to actually use e and the result, since they would need casts. Too bad.
Actually, it wouldn't be annoying, because you wouldn't use a general purpose map like you've shown. You'd use code shown in the parent.
while read line; do
host $line | head -n1 | awk '{print $1 " -> " $4}' &
done < domains.txtIf you gnu parallel (xargs++) installed - turn that second line into a script (and replace $line with $1).
cat domains.txt | parallel -n 1 -P 50 script.shFYI: On OSX, there is a Homebrew formula (brew install parallel).
cat domains.txt | parallel -P 100 --tag host | grep 1.2.3.4
Somewhat slower runtime than the go-solution, but may be faster to write. <domains.txt while read line; do
host $line | head -n1 &
done | awk '{print $1 " -> " $4}'Is it the two ", _" things?
file_in, _ := ioutil.ReadFile("domains.txt")
It says call ReadFile. In go a function can return multiple values. ReadFile returns an byte array and an error value. Normally you'd check to see if err is nil, if it is then no error happened. If it isn't you can check it out for information about the error and handle it.
A unique feature about go is that declaring a variable and never using it is an error and not a warning. Actually there aren't even compiler warnings. Either it is right or wrong. This means if he had called:
file_in, err := ioutil.ReadFile("domains.txt")
but never checked err it would not build. So to get the byte array but not the error you use the _ symbol to tell it to throw away that return value. This is what I was on about in that you have to actively ignore error handling if you want a return value.
Ahhh. Nice. That sounds like a feature I could get behind, too. Thank you.
That said, if this was just an adhoc job (to figure out which domains point to a specific IP address) you can just use "xargs -P" or GNU parallel and it becomes a pretty basic shell script, along the lines of: cat domains.txt | xargs -P 1000 -n 1 host
xargs -n1 -P8 dig A <hosts.txt | grep -v ';' | grep $TARGET_IP | sed 's/\.\s.*//' >hosts-matched.txthttp://www.gnu.org/software/parallel/man.html#differences_be...
https://gist.github.com/a803d86234e8d1fc5496
I also include a list of 100 domains in a domains.txt if anyone wants to try for themselves.
require "socket"
require "celluloid"
class IPGetter
include Celluloid
def get(url)
Socket.getaddrinfo(url, "http")[0][2]
end
end
pool = IPGetter.pool(size: 100)
ips = {}
File.open("domains.txt").each_line do |line|
line.chomp!
ips[line] = pool.future.get(line)
end
ips.each do |url, ip_future|
puts "#{url} => #{ip_future.value}"
endhttps://gist.github.com/bb01a85404e6b445dcb3#file_resolve_do...
% -*- erlang -*-
%%! -smp enable
worker(Hostname) ->
{ok, IP} = inet:getaddr(Hostname, inet),
io:format(
"~s => ~s~n",
[ip_to_string(IP)]
).
ip_to_string({N1,N2,N3,N4}) ->
io_lib:format(
"~w.~w.~w.~w",
[N1,N2,N3,N4]
).
main([DomainFile]) ->
{ok, Bin} = file:read_file(DomainFile),
String = binary_to_list(Bin),
Domains = string:tokens(String, "\n"),
plists:foreach(
fun(Domain) -> worker(Domain) end,
Domains
).
This uses https://github.com/eveel/plists/ def ip(host:String)=java.net.InetAddress.getAllByName(host)(0).getHostAddress
var map = Map[String,String]()
val hosts = List("google.com","twitter.com","facebook.com")
hosts.par.map(host => (host,ip(host))).foreach(hostip => map+=(hostip._1->hostip._2))
scala> map
res1: scala.collection.immutable.Map[String,String]
=Map(google.com -> 74.125.224.71,
twitter.com -> 199.59.150.7,
facebook.com -> 69.171.229.16) // clang -lcares -I/opt/local/include -L/opt/local/lib -o domains domains.c
#include <ares.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/queue.h>
#include <fcntl.h>
static void callback(void *arg, int status, int timeouts, struct hostent *host)
{
if(!host || status != ARES_SUCCESS){
printf("Failed to lookup %s\n", ares_strerror(status));
return;
}
char ip[INET6_ADDRSTRLEN];
int i = 0;
for (i = 0; host->h_addr_list[i]; ++i) {
inet_ntop(host->h_addrtype, host->h_addr_list[i], ip, sizeof(ip));
printf("%s: %s\n", host->h_name, ip);
}
}
static void wait_ares(ares_channel channel)
{
for(;;){
struct timeval *tvp, tv;
fd_set read_fds, write_fds;
int nfds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
nfds = ares_fds(channel, &read_fds, &write_fds);
if(nfds == 0){
break;
}
tvp = ares_timeout(channel, NULL, &tv);
select(nfds, &read_fds, &write_fds, NULL, tvp);
ares_process(channel, &read_fds, &write_fds);
}
}
struct Request {
ares_channel channel;
char* domain;
SLIST_ENTRY(Request) next;
};
int main(const int argc, const char* argv[]) {
SLIST_HEAD(Requests, Request) requests;
SLIST_INIT(&requests);
struct Request* last = NULL;
int status;
struct ares_options options;
int optmask = 0;
const char* path;
optmask |= ARES_OPT_TIMEOUTMS;
options.timeout = 1000;
status = ares_library_init(ARES_LIB_INIT_ALL);
if (status != ARES_SUCCESS){
printf("ares_library_init: %s\n", ares_strerror(status));
return 1;
}
for (int i = 1; (path = argv[i]) != NULL || i == 0; i++) {
int fd = open(path, O_RDONLY);
FILE* fp = fdopen(fd, "r");
char* buffer = NULL;
size_t bufferSize = 0;
int lineLength = 0;
while ((lineLength = getline(&buffer, &bufferSize, fp)) > 0) {
struct Request* request = malloc(sizeof(struct Request));
status = ares_init_options(&request->channel, &options, optmask);
if(status != ARES_SUCCESS) {
printf("ares_init_options: %s\n", ares_strerror(status));
continue;
}
buffer[lineLength - 1] = '\0';
request->domain = buffer;
buffer = NULL;
if (NULL == last) {
SLIST_INSERT_HEAD(&requests, request, next);
} else {
SLIST_INSERT_AFTER(last, request, next);
}
last = request;
ares_gethostbyname(request->channel, request->domain, AF_INET, callback, NULL);
}
}
while (!SLIST_EMPTY(&requests)) {
struct Request* request = SLIST_FIRST(&requests);
wait_ares(request->channel);
ares_destroy(request->channel);
free(request->domain);
SLIST_REMOVE_HEAD(&requests, next);
free(request);
}
ares_library_cleanup();
return 0;
}I have used Gevent a lot over the last couple years and see a lot of similarities in Go's concurrency, which I think is great.
Concurrency doesn't have to be about insane looking code!
it could look like:
from concurrent.futures import ThreadPoolExecutor as Pool
from socket import getaddrinfo
def lookup(domain):
try:
result = getaddrinfo(domain, 80)
exception Exception as e:
print("error %s -> %s" % (domain, e))
else:
print("done %s -> %s" % (domain, result))
nconcurrent = 20
with open('domains.txt') as file, Pool(nconcurrent) as pool:
for domain in (line.strip() for line in file):
pool.submit(lookup, domain)
To run multiple processes instead of threads, change the import to ProcessPoolExecutor.To support multiprocessing.Pool (for Python 2 where concurrent.futures is not in stdlib), replace pool.submit() with pool.apply_async() and use contextlib.closing() around the Pool().
Well you can do with callbacks anything that you can do with channels and goroutines. Go's primary appeal is that it makes concurrent[1] code easy to reason about, not that it enables you to do anything that you "couldn't do" otherwise.
Continuations are just GOTOs, and just like GOTOs, some people love them and some people hate them, but even people who like them can find them difficult in large doses. Goroutines and channels are nice, because they fit the structure of imperative code, whereas callbacks sort of resemble imperative code but "inside out".
[1] Note that I didn't say parallel!
you can get Go's parallelisms via CSP (e.g. python-csp, ruby-csp) and replace a lot of fragile threading/parallel code with it. i've been doing that in lieu of learning Go (i know i know .. i'm lazy) and been very pleased.
anyhow, many ways to skin cats. those are just two or three.
then again if it was a matter of "well, i had a problem to solve and i had a desire to explore another language, so solving it in that new language was a way to explore" then the point is moot.
however agreed 100% or more on the "code smell" of doing an event loop in Go.