Injektáž zdrojového kódu PL/pgSQL funkcí za účelem získání seznamu používaných funkcí
Počínaje 8.4 máme k dispozici jednoduchý intergrovaný profiler, který nám umožňuje zjistit, které zákaznické funkce se používají, a které nikoliv. Ve starších verzích tuto možnost nemáme a tak si musíme pomoci jinak. Jednou z možností je vložit do zdrojového kódu funkce volání jakési registrační funkce, která zaznamená do vybrané tabulky informaci o volání funkce. Pozor - na produkčním serveru může dojít (při vyšším zatížení) ke ztrátě výkonu. Oproti integrovanému profileru můžeme zjistit registrační funkcí více informací:
Registrační funkce je ukázkou funkce, která je napsána tak, aby neselhala za žádných okolností - nechceme, aby běh aplikace byl ovlivněn profilací:
/* * The invocation of this function is injected to custom function's source code. It increase * a function staticstic counter and logs a current query (only once per 5 minutes). */ CREATE OR REPLACE FUNCTION signal_func_used(_funcid oid, _funcname varchar, _lanname varchar) RETURNS void AS $$ BEGIN UPDATE user_stat_function_data SET calls = calls + 1 WHERE funcid = _funcid; IF NOT FOUND THEN -- use a advisory lock but only for this function (specified by func oid) PERFORM pg_advisory_lock(_funcid); -- chec it again - registration can be processed by other session UPDATE user_stat_function_data SET calls = calls + 1 WHERE funcid = _funcid; IF NOT FOUND THEN INSERT INTO user_stat_function_data(funcid,funcname,calls,lanname) VALUES(_funcid, _funcname, 1, _lanname); END IF; PERFORM pg_advisory_unlock(_funcid); END IF; /* * store current query to table activity_stat_data. Don't do it * more than once per five minutes - it reduce a log size */ IF NOT EXISTS(SELECT * FROM activity_stat_data WHERE funcid = _funcid AND query_start < current_timestamp - interval '5min') THEN INSERT INTO activity_stat_data SELECT _funcid, query_start, usename, client_addr, current_query FROM pg_stat_activity WHERE procpid = pg_backend_pid(); END IF; RETURN; END; $$ LANGUAGE plpgsql VOLATILE;
Pro hromadnou úpravu zdrojových kódů použijeme funkce:
/* * This function injects all custom functions in current database. It check if some * function is injected or not. When not, then put to begin of function's code a call * of function signal_function_used. */ CREATE OR REPLACE FUNCTION inject_watcher() RETURNS void AS $$ DECLARE r record; _sigfunc varchar; BEGIN FOR r IN SELECT p.oid, proname, nspname, prosrc, lanname FROM pg_proc p JOIN pg_language l ON l.oid = p.prolang JOIN pg_user u ON u.usesysid = p.proowner JOIN pg_namespace n ON n.oid = p.pronamespace WHERE lanname IN ('sql','plpgsql') AND usename <> 'postgres' AND nspname <> 'pg_catalog' AND proname NOT IN ('inject_watcher','signal_func_used') LOOP IF position('signal_func_used' in r.prosrc) = 0 THEN IF r.lanname = 'sql' THEN UPDATE pg_proc SET prosrc = 'SELECT signal_func_used(' || r.oid || ',' || quote_literal(r.proname) || ',' || quote_literal(r.lanname) || ');' || prosrc WHERE oid = r.oid; ELSEIF r.lanname = 'plpgsql' THEN _sigfunc := 'PERFORM signal_func_used(' || r.oid || ',' || quote_literal(r.proname) || ',' || quote_literal(r.lanname) || ');'; /* regularnim vyrazem musim najit prvni begin a za nej umistit signal */ UPDATE pg_proc SET prosrc = regexp_replace(prosrc,'begin','BEGIN ' || _sigfunc, 'i') WHERE oid = r.oid; END IF; END IF; END LOOP; RETURN; END; $$ LANGUAGE plpgsql; SELECT inject_watcher(); /* SELECT test1(); SELECT * FROM test2(); SELECT test3(10); */ /* * remove injected code */ CREATE OR REPLACE FUNCTION remove_watcher() RETURNS void AS $$ DECLARE r record; _sigfunc varchar; BEGIN FOR r IN SELECT p.oid, proname, nspname, prosrc, lanname FROM pg_proc p JOIN pg_language l ON l.oid = p.prolang JOIN pg_user u ON u.usesysid = p.proowner JOIN pg_namespace n ON n.oid = p.pronamespace WHERE lanname IN ('sql','plpgsql') AND usename <> 'postgres' AND nspname <> 'pg_catalog' AND proname NOT IN ('inject_watcher','signal_func_used') LOOP IF position('signal_func_used' in r.prosrc) <> 0 THEN IF r.lanname = 'sql' THEN _sigfunc := 'SELECT signal_func_used(' || r.oid || ',' || quote_literal(r.proname) || ',' || quote_literal(r.lanname) || ');'; ELSEIF r.lanname = 'plpgsql' THEN _sigfunc := 'PERFORM signal_func_used(' || r.oid || ',' || quote_literal(r.proname) || ',' || quote_literal(r.lanname) || ');'; END IF; UPDATE pg_proc SET prosrc = replace(prosrc, _sigfunc, '') WHERE oid = r.oid; END IF; END LOOP; RETURN; END; $$ LANGUAGE plpgsql; /* SELECT remove_watcher(); */
Zbývá pouze vytvořit logovací tabulky:
/* * necessary tables */ DROP TABLE IF EXISTS user_stat_function_data; DROP TABLE IF EXISTS activity_stat_data; CREATE TABLE user_stat_function_data( funcid oid PRIMARY KEY, funcname varchar, calls bigint, lanname varchar ); CREATE TABLE activity_stat_data( funcid oid, query_start timestamp with time zone, usename varchar, client_addr inet, query varchar );