How to use `otel_resource_detector` from OpenTelemetry in Elixir - erlang

I'm an Erlang/Elixir noob and I'm doing a research on how to use the otel_resource_detector in Elixir.
I've managed to get this working in Erlang some time ago, but I'm struggling to get things right in Elixir.
This is what I have in Erlang:
-module(extra_metadata).
-behaviour(otel_resource_detector).
-export([get_resource/1]).
get_resource(_) ->
Resource1 = otel_resource:create(otel_resource_app_env:parse(get_metadata("/data/extrametadata.properties")), []),
{ok, HiddenMetadataFile} = file:read_file("/data/hiddenpath.properties"),
Resource2 = otel_resource:create(otel_resource_app_env:parse(get_metadata(HiddenMetadataFile)), []),
otel_resource:merge(Resource1, Resource2).
get_metadata(FileName) ->
try
{ok, MetadataFile} = file:read_file(FileName),
Lines = binary:split(MetadataFile, <<"\n">>, [trim, global]),
make_tuples(Lines, [])
catch _:_ -> "Extra Metadata not found"
end.
make_tuples([Line|Lines], Acc) ->
[Key, Value] = binary:split(Line, <<"=">>),
make_tuples(Lines, [{Key, Value}|Acc]);
make_tuples([], Acc) -> Acc.
Full Erlang app here: https://github.com/julianocosta89/erlang_otel_hello_server/tree/main
I'm trying to make use of the otel_resource_detector from OpenTelemetry:
https://github.com/open-telemetry/opentelemetry-erlang/blob/37f3cecd9ad2a7b8f3b94c89118585991f0023b1/apps/opentelemetry/src/otel_resource_detector.erl
How would I use that in Elixir?

One can easily call erlang modules from elixir. The first module would look like
defmodule ExtraMetadata do
#behaviour :otel_resource_detector
def get_resource(_) do
resource1 =
:otel_resource.create(
:otel_resource_app_env.parse(
get_metadata("/data/extrametadata.properties")), [])
{:ok, hidden_metadata_file} =
File.read("/data/hiddenpath.properties")
resource2 =
:otel_resource.create(
:otel_resource_app_env.parse(
get_metadata(hidden_metadata_file)), [])
:otel_resource.merge(resource1, resource2)
end
defp get_metadata(file_name) do
try do
{:ok, metadata_file} = File.read(file_name)
lines = :binary.split(metadata_file, <<"\n">>, [:trim, :global])
make_tuples(lines)
catch
_ -> "Extra Metadata not found"
end
end
# Enum.map/2 would be probably more idiomatic
defp make_tuples(lines, acc \\ [])
defp make_tuples([line|lines], acc) do
[key, value] = :binary.split(line, <<"=">>)
make_tuples(lines, [{key, value}|cc])
defp make_tuples([], acc), do: acc
end
NB I obviously did not check the code above, some glitches might need some additional handling.

Here is a working snippet:
defmodule ExtraMetadata do
#behaviour :otel_resource_detector
def get_resource(_) do
lines = read_file("/data/extrametadata.properties") |> unwrap_lines
file_path = read_file("/data/hiddenpath.properties") |> unwrap_lines
lines2 = read_file(file_path) |> unwrap_lines
attributes = get_attributes(Enum.concat(lines, lines2))
:otel_resource.create(attributes)
end
defp unwrap_lines({:ok, lines}), do: lines
defp unwrap_lines({:error, _}), do: []
defp read_file(file_name) do
try do
{:ok, String.split(File.read!(file_name), "\n")}
rescue
File.Error ->
{:error, "File does not exist, safe to continue"}
end
end
defp get_attributes(lines) do
# Transform each string into a tuple
Enum.map(lines, fn(line) ->
if String.length(line) > 0 do
[key, value] = String.split(line, "=")
{key, value}
else
{:error, "Empty string"}
end
end)
end
end
I've also pushed the whole project here: https://github.com/julianocosta89/elixir-hello.

Related

How can I handle with duplicate items in Aerospike script

I have script, its work properly, but I have to update it. Script now add items without any checking for existing.
function put_page(rec, id, val)
local l = rec['h']
if l==nil then l = list() rec['id'] = id end
list.append(l, val)
rec['h'] = l
if aerospike:exists(rec) then aerospike:update(rec) else aerospike:create(rec) end
end
I try iterate over list with for value in list.iterator(l) and append item if value~=val, but it didnt work.
ID in function is solr document_id, val is users_id. I get example object from aerospike: (('contextChannel', 'ContextChannel', None, bytearray(b'E\xfb\xa3\xd0\r\xd6\r\J#f\xa8\xf6>y!\xd18=\x9b')), {'ttl': 2592000, 'gen': 8}, {'id': 'ALKSD4EW', 'h': []})
UPDATE
I try different variants, and this is worked:
function put_page(rec, id, val)
local l = rec['h']
local count = 0
if l==nil then l = list() rec['id'] = id end
for value in list.iterator(l) do
if (value ~= val) then count = count + 1 end
end
if (list.size(l) == count) then list.append(l, val) end
rec['h'] = l
if aerospike:exists(rec) then aerospike:update(rec) else aerospike:create(rec) end
end
Don't create a UDF for something that exists as a List API operation. UDFs will not perform as well, nor scale as well.
You can do this without a UDF. Here's an example of doing the same thing using the Python client.
from aerospike_helpers.operations import list_operations as lh
from aerospike_helpers.operations import operations as oh
list_policy = {
"list_order": aerospike.LIST_UNORDERED,
"write_flags": (aerospike.LIST_WRITE_ADD_UNIQUE |
aerospike.LIST_WRITE_NO_FAIL)
}
ops = [
oh.write('id', id),
lh.list_append('h', val, list_policy)
]
client.operate(key, ops)
I have an example of a similar thing at rbotzer/aerospike-cdt-examples.

Supervision tree failing to start

I'm trying to implement something like what is described in this answer, but I am getting errors like what I've included below when I compile the application.
** (Mix) Could not start application workers: Workers.Application.start(:normal, []) returned an error: shutdown: failed to start child: {Workers.UrlSupervisor, 2}
** (EXIT) already started: #PID<0.1034.0>
I'm not sure if I am inherently doing something I'm not allowed to here, or I've just made a little mistake.
For some context here are the supervisors:
defmodule Workers.Application do
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
#moduledoc false
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
url_workers = 1..100 |> Enum.map(fn (i) -> supervisor(Workers.UrlSupervisor, [i], [id: {Workers.UrlSupervisor, i}, restart: :temporary]) end)
domain_workers = 1..100 |> Enum.map(fn (i) -> supervisor(Workers.DomainSupervisor, [i], [id: {Workers.DomainSupervisor, i}, restart: :temporary]) end)
opts = [strategy: :one_for_one, name: Workers.Supervisor]
Supervisor.start_link(url_workers ++ domain_workers, opts)
end
end
defmodule Workers.UrlSupervisor do
def start_link(id) do
import Supervisor.Spec, warn: false
children = [worker(Task, [&Workers.Url.worker/0], [id: {Workers.Url, id}, restart: :permanent])]
opts = [strategy: :one_for_one, name: Workers.UrlSupervisor]
Supervisor.start_link(children, opts)
end
end
defmodule Workers.DomainSupervisor do
def start_link(id) do
import Supervisor.Spec, warn: false
children = [worker(Task, [&Workers.Domain.worker/0], [id: {Workers.Domain, id}, restart: :permanent])]
opts = [strategy: :one_for_one, name: Workers.DomainSupervisor]
Supervisor.start_link(children, opts)
end
end
And here is one of the workers (they look largely the same).
defmodule Workers.Domain do
def worker do
case Store.Domains.pop do
:empty ->
IO.puts "[Domain] none found, waiting..."
:timer.sleep(1000)
{crawl_id, domain} ->
IO.puts "[Domains] found a domain to check: #{domain}"
case Core.check_domain(domain) do
:error ->
Utils.insert(crawl_id, domain, false)
:registered ->
Utils.insert(crawl_id, domain, false)
:available ->
Utils.insert(crawl_id, domain, true)
end
end
worker()
end
end
In your Workers.Application when starting Supervisors, you'r providing unique ids, but they also should have unique names.
Try adding another keyword, something like name: :"url_supervisor_#{i}":
def start(_type, _args) do
import Supervisor.Spec, warn: false
url_workers = 1..100 |> Enum.map(fn (i) ->
supervisor(Workers.UrlSupervisor, [i],
[id: {Workers.UrlSupervisor, i},
name: :"url_supervisor_#{i}", # Name added here
restart: :temporary])
end)
domain_workers = 1..100 |> Enum.map(fn (i) ->
supervisor(Workers.DomainSupervisor, [i],
[id: {Workers.DomainSupervisor, i},
name: :"domain_supervisor_#{i}", # Name added here
restart: :temporary])
end)
opts = [strategy: :one_for_one, name: Workers.Supervisor]
Supervisor.start_link(url_workers ++ domain_workers, opts)
end

Monitor a stream with a Supervisor in Elixir

I use the ExTwitter-library to poll data using a stream like this:
stream = ExTwitter.stream_sample(receive_messages: true)
for message <- stream do
case message do
tweet = %ExTwitter.Model.Tweet{} ->
IO.puts "tweet = #{tweet.text}"
deleted_tweet = %ExTwitter.Model.DeletedTweet{} ->
IO.puts "deleted tweet = #{deleted_tweet.status[:id]}"
limit = %ExTwitter.Model.Limit{} ->
IO.puts "limit = #{limit.track}"
stall_warning = %ExTwitter.Model.StallWarning{} ->
IO.puts "stall warning = #{stall_warning.code}"
_ ->
IO.inspect message
end
end
and it's working great but now I want to monitor the stream with a Supervisor. What is the simplest way to do that?
The simplest way would be to put this code in a function in a new module, add a start_link function that simply invokes this function through spawn_link, and add that module as a worker to your Supervisor. Here's a simple example:
defmodule M do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
worker(M.Streamer, []),
]
opts = [strategy: :one_for_one, name: M.Supervisor]
Supervisor.start_link(children, opts)
end
end
defmodule M.Streamer do
def start_link do
{:ok, spawn_link(__MODULE__, :main, [])}
end
def main do
IO.inspect(self)
for i <- Stream.cycle([1, 2, 3]) do
IO.puts i
:timer.sleep(1000)
end
end
end
Demo:
#PID<0.85.0>
iex(1)> 1
2
3
1
2
3
1
2
Process.exit3pid(0, 85, 0), :kill)
#PID<0.88.0>
1
true
iex(2)> 2
3
1
2
3
1
2
3
Process.exit(pid(0, 88, 0), :kill)
#PID<0.90.0>
true
1
iex(3)> 2
3
1
2
3
This might be a little hard to follow as the output was happening while I was typing, but all the PID values were printed whenever Streamer started, the Process.exit lines are the code entered by me, and true is the return value of those calls. As you can see, whenever I killed the M.Streamer process, it was restarted by the Supervisor.

How do I make a path out of a hash?

I am trying to locate the route of the shortest path of a map(connected nodes with weight/distance).
Let's assume I have a hash like this:
{"A"=>{"B"=>1, "E"=>1}, "B"=>{"A"=>1, "C"=>1, "F"=>1}, "C"=>{"B"=>1, "D"=>1, "G"=>1}, "D"=>{"C"=>1, "H"=>1}, "E"=>{"F"=>1, "A"=>1, "I"=>1}, "F"=>{"E"=>1, "G"=>1, "B"=>1, "J"=>1}, "G"=>{"F"=>1, "H"=>1, "C"=>1, "K"=>1}, "H"=>{"G"=>1, "D"=>1, "L"=>1}, "I"=>{"J"=>1, "E"=>1, "M"=>1}, "J"=>{"I"=>1, "K"=>1, "F"=>1, "N"=>1}, "K"=>{"J"=>1, "L"=>1, "G"=>1, "O"=>1}, "L"=>{"K"=>1, "H"=>1, "P"=>1}, "M"=>{"N"=>1, "I"=>1}, "N"=>{"M"=>1, "O"=>1, "J"=>1}, "O"=>{"N"=>1, "P"=>1, "K"=>1}, "P"=>{"O"=>1, "L"=>1}}
Now I want to traverse and make a path from this hash. For example:
From Source A to Destination: L
Output should be: either A -> E -> I -> J -> K -> L or A -> B -> C -> D -> H -> L.
Here is the function I wrote:
def find_path(src, dst, init = [])
path = [src]
neighbors = self.neighbors(src)
puts "src: #{src}"
puts "dst: #{dst}"
# puts "node: #{node}"
puts "init: #{init}"
puts "path: #{path}"
puts "----\n"
if neighbors.include?(dst)
path.push(dst)
else
path.push(#nodes[src].keys.map{|k| k unless init.flatten.include? k }.reject(&:blank?).each{|key| self.find_path(key, dst, init << path) } )
end
return path
end
But, this prints only : ["A", ["B", "E"]]
Which is not the desired output, can anybody tell me how do I go make this work? Thanks.
Update: This was used for locating the route of the shortest path of a map(connected nodes with weight/distance). Here are the details on what I was trying to achieve in this question: https://gist.github.com/suryart/6439102
The original hash:
h = {"A"=>{"B"=>1, "E"=>1}, "B"=>{"A"=>1, "C"=>1, "F"=>1}, "C"=>{"B"=>1, "D"=>1, "G"=>1}, "D"=>{"C"=>1, "H"=>1}, "E"=>{"F"=>1, "A"=>1, "I"=>1}, "F"=>{"E"=>1, "G"=>1, "B"=>1, "J"=>1}, "G"=>{"F"=>1, "H"=>1, "C"=>1, "K"=>1}, "H"=>{"G"=>1, "D"=>1, "L"=>1}, "I"=>{"J"=>1, "E"=>1, "M"=>1}, "J"=>{"I"=>1, "K"=>1, "F"=>1, "N"=>1}, "K"=>{"J"=>1, "L"=>1, "G"=>1, "O"=>1}, "L"=>{"K"=>1, "H"=>1, "P"=>1}, "M"=>{"N"=>1, "I"=>1}, "N"=>{"M"=>1, "O"=>1, "J"=>1}, "O"=>{"N"=>1, "P"=>1, "K"=>1}, "P"=>{"O"=>1, "L"=>1}}
Converting the original hash since its format sucks:
h.keys.each{|k| h[k] = h[k].keys}
h.default = []
The method:
def find_path h, src, dst
paths = [[src]]
loop do
paths = paths.flat_map do |path|
h[path.last].map do |nekst|
a = [*path, nekst]
a.last == dst ? (return a) : a
end
end
end
end
Trying it:
find_path(h, "A", "L")
# => ["A", "B", "C", "D", "H", "L"]
Notice that if there is no solution, then the loop may run forever. You might want to limit that by adding a limit to the length.

Arel producing different SQL output

I'm writing a gem for Rails that makes use of Arel. I have run into a case where the output generated by a subnode is different depending on how the output is generated. Below is a test case. Is this a bug or am I doing something wrong?
it 'should generate the same output' do
def ts_language; 'english'; end
def searchable_columns; [:id, :name]; end
def ts_column_sets
column_sets = if searchable_columns[0].is_a?(Array)
searchable_columns.map do |array|
array.map { |c| Table.new(:users)[c] }
end
else
searchable_columns.map { |c| Table.new(:users)[c] }.map { |x| [x] }
end
end
def ts_vectors
ts_column_sets.map do |columns|
coalesce = columns[1..-1].inject(columns[0]) do |memo, column|
Arel::Nodes::InfixOperation.new('||', memo, column)
end
coalesce = Arel::Nodes::InfixOperation.new('::', Arel::Nodes::NamedFunction.new('COALESCE', [coalesce, '']), Arel::Nodes::SqlLiteral.new('text'))
Arel::Nodes::NamedFunction.new('to_tsvector', [ts_language, coalesce])
end
end
def ts_query(query)
querytext = query.is_a?(Array) ? query.map(&:to_s).map(&:strip) : query.to_s.strip.split(" ")
querytext = querytext[1..-1].inject(querytext[0]) { |memo, c| memo + ' & ' + c }
querytext << ':*'
querytext = Arel::Nodes::InfixOperation.new('::', querytext, Arel::Nodes::SqlLiteral.new('text'))
Arel::Nodes::NamedFunction.new('to_tsquery', ['english', querytext])
end
node = Arel::Nodes::InfixOperation.new('##', ts_vectors[0], ts_query(0))
assert_equal 'to_tsvector(\'english\', COALESCE("users"."id", 0) :: text)', node.left.to_sql
assert_equal 'to_tsquery(\'english\', \'0:*\' :: text)', node.right.to_sql
assert_equal 'to_tsvector(\'english\', COALESCE("users"."id", 0) :: text) ## to_tsquery(0, \'0:*\' :: text)', node.to_sql
end
The line
assert_equal 'to_tsquery(\'english\', \'0:*\' :: text)', node.right.to_sql
is correct but the line
assert_equal 'to_tsvector(\'english\', COALESCE("users"."id", 0) :: text) ## to_tsquery(0, \'0:*\' :: text)', node.to_sql
results in an error and the output is:
'to_tsvector(\'english\', COALESCE("users"."id", 0) :: text) ## to_tsquery(0, 0 :: text)'

Resources