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_app_xref(conn, {caller, callee}) do
caller = get_id_by_name!(conn, "apps", caller)
callee = get_id_by_name!(conn, "apps", callee)
case {caller, callee} do
{:not_found, _} ->
:missing_ref
{_, :not_found} ->
:missing_ref
{caller_id, callee_id} ->
:ok =
execute(
conn,
"INSERT INTO apps_xrefs (caller_id, callee_id)
VALUES (?1, ?2);",
[caller_id, callee_id]
)
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, is_test) VALUES (?1, ?2, ?3, ?4);",
[mod.name, mod.num_lines, mod.path, boolean_to_integer(mod.is_test)]
)
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
has_struct INTEGER, -- does the module define a struct?
has_ecto_schema INTEGER, -- does the module define an ecto schema?
is_test INTEGER -- if the module is a test?
);",
&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
is_external INTEGER -- if the application is external
)",
&save_app/2
)
end
def save_apps_xrefs(apps_xrefs, db_name \\ default_db_name()) do
:ok =
dump_all(
apps_xrefs,
db_name,
"apps_xrefs",
"CREATE TABLE apps_xrefs (
-- table representing cross references within apps
caller_id INTEGER REFERENCES apps, -- calling app
callee_id INTEGER REFERENCES apps -- called app
);",
&save_app_xref/2
)
end
def add_structs(modules, db_name \\ default_db_name()) do
{:ok, conn} = DB.open(db_name)
Enum.each(modules, &add_struct(conn, &1))
:ok =
execute(
conn,
"UPDATE modules
SET has_struct = FALSE
WHERE has_struct IS NULL",
[]
)
DB.close(conn)
end
def save_behaviours(behaviours, db_name \\ default_db_name()) do
:ok =
dump_all(
behaviours,
db_name,
"behaviours",
"CREATE TABLE behaviours (
-- table linking behaviours with modules
id INTEGER PRIMARY KEY,
name TEXT, -- name of the implemented behaviour
module_id INTEGER REFERENCES modules -- module that implements the behaviour
)",
&save_behaviour/2
)
end
def save_credo_issues(issues, db_name \\ default_db_name()) do
:ok =
dump_all(
issues,
db_name,
"credo_issues",
"CREATE TABLE credo_issues (
-- table for storing credo issues
id INTEGER PRIMARY KEY,
check_module TEXT, -- name of the failed check
line_no INTEGER, -- location of the triggering expression
severity INTEGER, -- how important is the failed check
module_id INTEGER REFERENCES modules -- affected module
);",
&save_credo_issue/2
)
end
def save_credo_issue(conn, issue) do
mod_name = module_to_string(issue.module)
case get_id_by_name!(conn, "modules", mod_name) do
:not_found ->
:mising_ref
mod_id ->
:ok =
execute(
conn,
"INSERT INTO credo_issues (
check_module,
line_no,
severity,
module_id)
VALUES (?1, ?2, ?3, ?4);",
[
issue.check,
issue.line_no,
issue.severity,
mod_id
]
)
end
end
def add_ecto_schemas(modules, db_name \\ default_db_name()) do
{:ok, conn} = DB.open(db_name)
Enum.each(modules, &add_ecto_schema(conn, &1))
:ok =
execute(
conn,
"UPDATE modules
SET has_ecto_schema = FALSE
WHERE has_ecto_schema IS NULL",
[]
)
DB.close(conn)
end
defp save_app(conn, %{app: app, is_external: is_external}) do
:ok =
execute(
conn,
"INSERT INTO apps (name, is_external) VALUES (?1, ?2);",
[Atom.to_string(app), boolean_to_integer(is_external)]
)
end
defp add_struct(conn, module) do
:ok =
execute(
conn,
"UPDATE modules
SET has_struct = TRUE
WHERE name = ?1",
[
module
]
)
end
def add_ecto_associations(associations, db_name \\ default_db_name()) do
{:ok, conn} = DB.open(db_name)
Enum.each(associations, &save_xref(conn, &1))
DB.close(conn)
end
defp save_behaviour(conn, behaviour) do
case get_id_by_name!(conn, "modules", behaviour.module) do
:not_found ->
:missing_ref
mod_id ->
:ok =
execute(
conn,
"INSERT INTO behaviours (
name,
module_id)
VALUES (?1, ?2);",
[
behaviour.name,
mod_id
]
)
end
end
defp add_ecto_schema(conn, module) do
:ok =
execute(
conn,
"UPDATE modules
SET has_ecto_schema = TRUE
WHERE name = ?1;",
[
module
]
)
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
defp boolean_to_integer(value), do: if(value, do: 1, else: 0)
end