Current section

Files

Jump to
archeometer lib archeometer util dump_stats.ex
Raw

lib/archeometer/util/dump_stats.ex

defmodule Archeometer.Util.DumpStats do
@moduledoc false
use Archeometer.Repo
def add_modules_app_id(app_modules, db_name \\ default_db_name()) do
{:ok, conn} = DB.open(db_name)
Enum.each(app_modules, &assign_modules_app_id(conn, &1))
DB.close(conn)
end
defp assign_modules_app_id(conn, %{app: app, modules: modules}) do
case get_id_by_name!(conn, "apps", app |> Atom.to_string()) do
:not_found ->
:missing_ref
app_id ->
Enum.each(modules, &add_module_app_id(conn, app_id, &1))
end
end
defp module_to_string(mod) when is_atom(mod), do: Macro.to_string(mod)
defp module_to_string(mod) when is_bitstring(mod), do: mod
defp add_module_app_id(conn, app_id, module) do
:ok =
execute(
conn,
"UPDATE modules
SET app_id = ?1
WHERE name = ?2",
[app_id, module_to_string(module)]
)
end
def add_module_coverages(covs, db_name \\ default_db_name()) do
{:ok, conn} = DB.open(db_name)
Enum.each(covs, &add_module_coverage(conn, &1))
DB.close(conn)
end
defp add_module_coverage(conn, cov) do
case get_id_by_name!(conn, "modules", module_to_string(cov.module)) do
:not_found ->
:missing_ref
mod_id ->
:ok =
execute(
conn,
"UPDATE modules
SET coverage = ?1
WHERE id = ?2;",
[cov.coverage, mod_id]
)
end
end
def add_function_coverages(covs, db_name \\ default_db_name()) do
{:ok, conn} = DB.open(db_name)
Enum.each(covs, &add_function_coverage(conn, &1))
DB.close(conn)
end
def add_function_coverage(conn, cov) do
case get_id_by_name!(conn, "modules", module_to_string(cov.module)) do
:not_found ->
:missing_ref
mod_id ->
:ok =
execute(
conn,
"UPDATE functions
SET coverage = ?1
WHERE module_id = ?2
AND name = ?3
AND num_args = ?4;",
[
cov.coverage,
mod_id,
cov.name |> Macro.to_string() |> String.replace_prefix(":", ""),
cov.num_args
]
)
end
end
defp get_id_by_name!(conn, table_name, name) do
{:ok, query} = DB.prepare(conn, "SELECT id FROM #{table_name} WHERE name = ?1;")
:ok = DB.bind(conn, query, [name])
case DB.step(conn, query) do
{:row, [module_id]} ->
:done = DB.step(conn, query)
:ok = DB.release(conn, query)
module_id
:done ->
:ok = DB.release(conn, query)
:not_found
end
end
defp save_xref(conn, xref) do
caller = get_id_by_name!(conn, "modules", xref.caller |> module_to_string())
callee = get_id_by_name!(conn, "modules", xref.callee |> module_to_string())
case {caller, callee} do
{:not_found, _} ->
:missing_ref
{_, :not_found} ->
:missing_ref
{caller_id, callee_id} ->
:ok =
execute(
conn,
"INSERT INTO xrefs (caller_id, callee_id, type, line)
VALUES (?1, ?2, ?3, ?4);",
[caller_id, callee_id, xref.type, xref.line]
)
end
end
def save_xrefs(refs, db_name \\ default_db_name()) do
:ok =
dump_all(
refs,
db_name,
"xrefs",
"CREATE TABLE xrefs (
-- table representing cross references in the project
caller_id INTEGER REFERENCES modules, -- calling module
callee_id INTEGER REFERENCES modules, -- called module
type TEXT, -- type of reference (function, struct, macro, etc)
line INTEGER -- line in the file where the reference was made
);",
&save_xref/2
)
end
defp save_module(conn, mod) do
:ok =
execute(
conn,
"INSERT INTO modules (name, num_lines, path) VALUES (?1, ?2, ?3);",
[mod.name, mod.num_lines, mod.path]
)
end
def save_modules(mods, db_name \\ default_db_name()) do
:ok =
dump_all(
mods,
db_name,
"modules",
"CREATE TABLE modules (
-- table to store all the modules information
id INTEGER PRIMARY KEY,
name TEXT, -- name of the module
num_lines INTEGER, -- lenght in lines of its declaration
coverage REAL, -- percentage of tested lines
path TEXT, -- file path where the module was defined
app_id INTEGER REFERENCES apps -- reference to the associated OTP application
);",
&save_module/2
)
end
defp save_def(conn, df, table_name) do
case get_id_by_name!(conn, "modules", df.module) do
:not_found ->
:missing_ref
mod_id ->
{arg_names, args_len} = format_def_args_info(df.args)
:ok =
execute(
conn,
"INSERT INTO #{table_name} (
name,
module_id,
cc,
num_lines,
num_args,
arg_names,
type
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7);",
[
Macro.to_string(df.name) |> String.replace_prefix(":", ""),
mod_id,
df.cc,
df.num_lines,
args_len,
arg_names,
df.type
]
)
end
end
defp format_def_args_info(args) do
if is_list(args) do
{
args |> Enum.map_join(",", &Macro.to_string/1),
length(args)
}
else
{nil, 0}
end
end
def save_defs(defs, table_name, db_name \\ default_db_name()) do
:ok =
dump_all(
defs,
db_name,
table_name,
"CREATE TABLE #{table_name} (
-- table representing #{table_name}s
id INTEGER PRIMARY KEY,
name TEXT, -- the name used in the declaration
module_id INTEGER REFERENCES modules, -- a reference to the owning module
cc INTEGER, -- cyclomatic complexity of the definition
num_lines INTEGER, -- lenght in lines of the definition
num_args INTEGER, -- number of parameters it receives
arg_names TEXT, -- comma separated list of parameters names
type TEXT, -- visibility and type (def or defmacro) of the definition
coverage REAL -- percentage of test coverega, might not be too accurate
);",
&save_def(&1, &2, table_name)
)
end
def save_apps(apps, db_name \\ default_db_name()) do
:ok =
dump_all(
apps,
db_name,
"apps",
"CREATE TABLE apps(
-- table representing the OTP application
id INTEGER PRIMARY KEY,
name TEXT -- the name of the OTP application
)",
&save_app/2
)
end
defp save_app(conn, app) do
:ok =
execute(
conn,
"INSERT INTO apps (name) VALUES (?1);",
[app |> Atom.to_string()]
)
end
defp dump_all(elems, db_name, table_name, create_query, save_fn) do
{:ok, conn} = DB.open(db_name)
:ok = DB.execute(conn, "DROP TABLE IF EXISTS #{table_name};")
:ok = DB.execute(conn, create_query)
Enum.each(elems, &save_fn.(conn, &1))
DB.close(conn)
end
end